Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
BeanReader |
|
| 1.5454545454545454;1.545 |
1 | /* |
|
2 | * Copyright 2001-2004 The Apache Software Foundation. |
|
3 | * |
|
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
|
5 | * you may not use this file except in compliance with the License. |
|
6 | * You may obtain a copy of the License at |
|
7 | * |
|
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
9 | * |
|
10 | * Unless required by applicable law or agreed to in writing, software |
|
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
13 | * See the License for the specific language governing permissions and |
|
14 | * limitations under the License. |
|
15 | */ |
|
16 | package org.apache.commons.betwixt.io; |
|
17 | ||
18 | import java.beans.IntrospectionException; |
|
19 | import java.io.IOException; |
|
20 | import java.util.HashSet; |
|
21 | import java.util.Set; |
|
22 | ||
23 | import javax.xml.parsers.SAXParser; |
|
24 | ||
25 | import org.apache.commons.betwixt.BindingConfiguration; |
|
26 | import org.apache.commons.betwixt.ElementDescriptor; |
|
27 | import org.apache.commons.betwixt.XMLBeanInfo; |
|
28 | import org.apache.commons.betwixt.XMLIntrospector; |
|
29 | import org.apache.commons.betwixt.io.read.ReadConfiguration; |
|
30 | import org.apache.commons.betwixt.io.read.ReadContext; |
|
31 | import org.apache.commons.digester.Digester; |
|
32 | import org.apache.commons.digester.ExtendedBaseRules; |
|
33 | import org.apache.commons.digester.RuleSet; |
|
34 | import org.apache.commons.logging.Log; |
|
35 | import org.apache.commons.logging.LogFactory; |
|
36 | import org.xml.sax.InputSource; |
|
37 | import org.xml.sax.SAXException; |
|
38 | import org.xml.sax.XMLReader; |
|
39 | ||
40 | /** <p><code>BeanReader</code> reads a tree of beans from an XML document.</p> |
|
41 | * |
|
42 | * <p>Call {@link #registerBeanClass(Class)} or {@link #registerBeanClass(String, Class)} |
|
43 | * to add rules to map a bean class.</p> |
|
44 | * |
|
45 | * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> |
|
46 | */ |
|
47 | public class BeanReader extends Digester { |
|
48 | ||
49 | /** Introspector used */ |
|
50 | 968 | private XMLIntrospector introspector = new XMLIntrospector(); |
51 | /** Log used for logging (Doh!) */ |
|
52 | 968 | private Log log = LogFactory.getLog( BeanReader.class ); |
53 | /** The registered classes */ |
|
54 | 968 | private Set registeredClasses = new HashSet(); |
55 | /** Dynamic binding configuration settings */ |
|
56 | 968 | private BindingConfiguration bindingConfiguration = new BindingConfiguration(); |
57 | /** Reading specific configuration settings */ |
|
58 | 968 | private ReadConfiguration readConfiguration = new ReadConfiguration(); |
59 | ||
60 | /** |
|
61 | * Construct a new BeanReader with default properties. |
|
62 | */ |
|
63 | 968 | public BeanReader() { |
64 | // TODO: now we require extended rules may need to document this |
|
65 | 968 | setRules(new ExtendedBaseRules()); |
66 | 968 | } |
67 | ||
68 | /** |
|
69 | * Construct a new BeanReader, allowing a SAXParser to be passed in. This |
|
70 | * allows BeanReader to be used in environments which are unfriendly to |
|
71 | * JAXP1.1 (such as WebLogic 6.0). Thanks for the request to change go to |
|
72 | * James House (james@interobjective.com). This may help in places where |
|
73 | * you are able to load JAXP 1.1 classes yourself. |
|
74 | * |
|
75 | * @param parser use this <code>SAXParser</code> |
|
76 | */ |
|
77 | public BeanReader(SAXParser parser) { |
|
78 | 0 | super(parser); |
79 | 0 | setRules(new ExtendedBaseRules()); |
80 | 0 | } |
81 | ||
82 | /** |
|
83 | * Construct a new BeanReader, allowing an XMLReader to be passed in. This |
|
84 | * allows BeanReader to be used in environments which are unfriendly to |
|
85 | * JAXP1.1 (such as WebLogic 6.0). Note that if you use this option you |
|
86 | * have to configure namespace and validation support yourself, as these |
|
87 | * properties only affect the SAXParser and emtpy constructor. |
|
88 | * |
|
89 | * @param reader use this <code>XMLReader</code> as source for SAX events |
|
90 | */ |
|
91 | public BeanReader(XMLReader reader) { |
|
92 | 0 | super(reader); |
93 | 0 | setRules(new ExtendedBaseRules()); |
94 | 0 | } |
95 | ||
96 | ||
97 | /** |
|
98 | * <p>Register a bean class and add mapping rules for this bean class.</p> |
|
99 | * |
|
100 | * <p>A bean class is introspected when it is registered. |
|
101 | * It will <strong>not</strong> be introspected again even if the introspection |
|
102 | * settings are changed. |
|
103 | * If re-introspection is required, then {@link #deregisterBeanClass} must be called |
|
104 | * and the bean re-registered.</p> |
|
105 | * |
|
106 | * <p>A bean class can only be registered once. |
|
107 | * If the same class is registered a second time, this registration will be ignored. |
|
108 | * In order to change a registration, call {@link #deregisterBeanClass} |
|
109 | * before calling this method.</p> |
|
110 | * |
|
111 | * <p>All the rules required to digest this bean are added when this method is called. |
|
112 | * Other rules that you want to execute before these should be added before this |
|
113 | * method is called. |
|
114 | * Those that should be executed afterwards, should be added afterwards.</p> |
|
115 | * |
|
116 | * @param beanClass the <code>Class</code> to be registered |
|
117 | * @throws IntrospectionException if the bean introspection fails |
|
118 | */ |
|
119 | public void registerBeanClass(Class beanClass) throws IntrospectionException { |
|
120 | 760 | if ( ! registeredClasses.contains( beanClass ) ) { |
121 | 760 | register(beanClass, null); |
122 | ||
123 | } else { |
|
124 | 0 | if ( log.isWarnEnabled() ) { |
125 | 0 | log.warn("Cannot add class " + beanClass.getName() + " since it already exists"); |
126 | } |
|
127 | } |
|
128 | 760 | } |
129 | ||
130 | /** |
|
131 | * Registers the given class at the given path. |
|
132 | * @param beanClass <code>Class</code> for binding |
|
133 | * @param path the path at which the bean class should be registered |
|
134 | * or null if the automatic path is to be used |
|
135 | * @throws IntrospectionException |
|
136 | */ |
|
137 | private void register(Class beanClass, String path) throws IntrospectionException { |
|
138 | 1539 | if ( log.isTraceEnabled() ) { |
139 | 0 | log.trace( "Registering class " + beanClass ); |
140 | } |
|
141 | 1539 | XMLBeanInfo xmlInfo = introspector.introspect( beanClass ); |
142 | 1539 | registeredClasses.add( beanClass ); |
143 | ||
144 | 1539 | ElementDescriptor elementDescriptor = xmlInfo.getElementDescriptor(); |
145 | ||
146 | 1539 | if (path == null) { |
147 | 1448 | path = elementDescriptor.getQualifiedName(); |
148 | } |
|
149 | ||
150 | 1539 | if (log.isTraceEnabled()) { |
151 | 0 | log.trace("Added path: " + path + ", mapped to: " + beanClass.getName()); |
152 | } |
|
153 | 1539 | addBeanCreateRule( path, elementDescriptor, beanClass ); |
154 | 1539 | } |
155 | ||
156 | /** |
|
157 | * <p>Registers a bean class |
|
158 | * and add mapping rules for this bean class at the given path expression.</p> |
|
159 | * |
|
160 | * |
|
161 | * <p>A bean class is introspected when it is registered. |
|
162 | * It will <strong>not</strong> be introspected again even if the introspection |
|
163 | * settings are changed. |
|
164 | * If re-introspection is required, then {@link #deregisterBeanClass} must be called |
|
165 | * and the bean re-registered.</p> |
|
166 | * |
|
167 | * <p>A bean class can only be registered once. |
|
168 | * If the same class is registered a second time, this registration will be ignored. |
|
169 | * In order to change a registration, call {@link #deregisterBeanClass} |
|
170 | * before calling this method.</p> |
|
171 | * |
|
172 | * <p>All the rules required to digest this bean are added when this method is called. |
|
173 | * Other rules that you want to execute before these should be added before this |
|
174 | * method is called. |
|
175 | * Those that should be executed afterwards, should be added afterwards.</p> |
|
176 | * |
|
177 | * @param path the xml path expression where the class is to registered. |
|
178 | * This should be in digester path notation |
|
179 | * @param beanClass the <code>Class</code> to be registered |
|
180 | * @throws IntrospectionException if the bean introspection fails |
|
181 | */ |
|
182 | public void registerBeanClass(String path, Class beanClass) throws IntrospectionException { |
|
183 | 91 | if ( ! registeredClasses.contains( beanClass ) ) { |
184 | ||
185 | 91 | register(beanClass, path); |
186 | ||
187 | } else { |
|
188 | 0 | if ( log.isWarnEnabled() ) { |
189 | 0 | log.warn("Cannot add class " + beanClass.getName() + " since it already exists"); |
190 | } |
|
191 | } |
|
192 | 91 | } |
193 | ||
194 | /** |
|
195 | * <p>Registers a class with a multi-mapping. |
|
196 | * This mapping is specified by the multi-mapping document |
|
197 | * contained in the given <code>InputSource</code>. |
|
198 | * </p><p> |
|
199 | * <strong>Note:</strong> the custom mappings will be registered with |
|
200 | * the introspector. This must remain so for the reading to work correctly |
|
201 | * It is recommended that use of the pre-registeration process provided |
|
202 | * by {@link XMLIntrospector#register} be considered as an alternative to this method. |
|
203 | * </p> |
|
204 | * @see #registerBeanClass(Class) since the general notes given there |
|
205 | * apply equally to this |
|
206 | * @see XMLIntrospector#register(InputSource) for more details on the multi-mapping format |
|
207 | * @since 0.7 |
|
208 | * @param mapping <code>InputSource</code> giving the multi-mapping document specifying |
|
209 | * the mapping |
|
210 | * @throws IntrospectionException |
|
211 | * @throws SAXException |
|
212 | * @throws IOException |
|
213 | */ |
|
214 | public void registerMultiMapping(InputSource mapping) throws IntrospectionException, IOException, SAXException { |
|
215 | 149 | Class[] mappedClasses = introspector.register(mapping); |
216 | 824 | for (int i=0, size=mappedClasses.length; i<size; i++) |
217 | { |
|
218 | 675 | Class beanClass = mappedClasses[i]; |
219 | 675 | if ( ! registeredClasses.contains( beanClass ) ) { |
220 | 675 | register(beanClass, null); |
221 | ||
222 | } |
|
223 | } |
|
224 | 149 | } |
225 | ||
226 | /** |
|
227 | * <p>Registers a class with a custom mapping. |
|
228 | * This mapping is specified by the standard dot betwixt document |
|
229 | * contained in the given <code>InputSource</code>. |
|
230 | * </p><p> |
|
231 | * <strong>Note:</strong> the custom mapping will be registered with |
|
232 | * the introspector. This must remain so for the reading to work correctly |
|
233 | * It is recommended that use of the pre-registeration process provided |
|
234 | * by {@link XMLIntrospector#register} be considered as an alternative to this method. |
|
235 | * </p> |
|
236 | * @see #registerBeanClass(Class) since the general notes given there |
|
237 | * apply equally to this |
|
238 | * @since 0.7 |
|
239 | * @param mapping <code>InputSource</code> giving the dot betwixt document specifying |
|
240 | * the mapping |
|
241 | * @param beanClass <code>Class</code> that should be register |
|
242 | * @throws IntrospectionException |
|
243 | * @throws SAXException |
|
244 | * @throws IOException |
|
245 | */ |
|
246 | public void registerBeanClass(InputSource mapping, Class beanClass) throws IntrospectionException, IOException, SAXException { |
|
247 | 13 | if ( ! registeredClasses.contains( beanClass ) ) { |
248 | ||
249 | 13 | introspector.register( beanClass, mapping ); |
250 | 13 | register(beanClass, null); |
251 | ||
252 | } else { |
|
253 | 0 | if ( log.isWarnEnabled() ) { |
254 | 0 | log.warn("Cannot add class " + beanClass.getName() + " since it already exists"); |
255 | } |
|
256 | } |
|
257 | 13 | } |
258 | ||
259 | /** |
|
260 | * <p>Flush all registered bean classes. |
|
261 | * This allows all bean classes to be re-registered |
|
262 | * by a subsequent calls to <code>registerBeanClass</code>.</p> |
|
263 | * |
|
264 | * <p><strong>Note</strong> that deregistering a bean does <strong>not</strong> |
|
265 | * remove the Digester rules associated with that bean.</p> |
|
266 | * @since 0.5 |
|
267 | */ |
|
268 | public void flushRegisteredBeanClasses() { |
|
269 | 0 | registeredClasses.clear(); |
270 | 0 | } |
271 | ||
272 | /** |
|
273 | * <p>Remove the given class from the register. |
|
274 | * Calling this method will allow the bean class to be re-registered |
|
275 | * by a subsequent call to <code>registerBeanClass</code>. |
|
276 | * This allows (for example) a bean to be reintrospected after a change |
|
277 | * to the introspection settings.</p> |
|
278 | * |
|
279 | * <p><strong>Note</strong> that deregistering a bean does <strong>not</strong> |
|
280 | * remove the Digester rules associated with that bean.</p> |
|
281 | * |
|
282 | * @param beanClass the <code>Class</code> to remove from the set of registered bean classes |
|
283 | * @since 0.5 |
|
284 | */ |
|
285 | public void deregisterBeanClass( Class beanClass ) { |
|
286 | 13 | registeredClasses.remove( beanClass ); |
287 | 13 | } |
288 | ||
289 | // Properties |
|
290 | //------------------------------------------------------------------------- |
|
291 | ||
292 | /** |
|
293 | * <p> Get the introspector used. </p> |
|
294 | * |
|
295 | * <p> The {@link XMLBeanInfo} used to map each bean is |
|
296 | * created by the <code>XMLIntrospector</code>. |
|
297 | * One way in which the mapping can be customized is by |
|
298 | * altering the <code>XMLIntrospector</code>. </p> |
|
299 | * |
|
300 | * @return the <code>XMLIntrospector</code> used for the introspection |
|
301 | */ |
|
302 | public XMLIntrospector getXMLIntrospector() { |
|
303 | 273 | return introspector; |
304 | } |
|
305 | ||
306 | ||
307 | /** |
|
308 | * <p> Set the introspector to be used. </p> |
|
309 | * |
|
310 | * <p> The {@link XMLBeanInfo} used to map each bean is |
|
311 | * created by the <code>XMLIntrospector</code>. |
|
312 | * One way in which the mapping can be customized is by |
|
313 | * altering the <code>XMLIntrospector</code>. </p> |
|
314 | * |
|
315 | * @param introspector use this introspector |
|
316 | */ |
|
317 | public void setXMLIntrospector(XMLIntrospector introspector) { |
|
318 | 260 | this.introspector = introspector; |
319 | 260 | } |
320 | ||
321 | /** |
|
322 | * <p> Get the current level for logging. </p> |
|
323 | * |
|
324 | * @return the <code>Log</code> implementation this class logs to |
|
325 | */ |
|
326 | public Log getLog() { |
|
327 | 0 | return log; |
328 | } |
|
329 | ||
330 | /** |
|
331 | * <p> Set the current logging level. </p> |
|
332 | * |
|
333 | * @param log the <code>Log</code>implementation to use for logging |
|
334 | */ |
|
335 | public void setLog(Log log) { |
|
336 | 0 | this.log = log; |
337 | 0 | setLogger(log); |
338 | 0 | } |
339 | ||
340 | /** |
|
341 | * Should the reader use <code>ID</code> attributes to match beans. |
|
342 | * |
|
343 | * @return true if <code>ID</code> and <code>IDREF</code> |
|
344 | * attributes should be used to match instances |
|
345 | * @deprecated 0.5 use {@link BindingConfiguration#getMapIDs} |
|
346 | */ |
|
347 | public boolean getMatchIDs() { |
|
348 | 0 | return getBindingConfiguration().getMapIDs(); |
349 | } |
|
350 | ||
351 | /** |
|
352 | * Set whether the read should use <code>ID</code> attributes to match beans. |
|
353 | * |
|
354 | * @param matchIDs pass true if <code>ID</code>'s should be matched |
|
355 | * @deprecated 0.5 use {@link BindingConfiguration#setMapIDs} |
|
356 | */ |
|
357 | public void setMatchIDs(boolean matchIDs) { |
|
358 | 0 | getBindingConfiguration().setMapIDs( matchIDs ); |
359 | 0 | } |
360 | ||
361 | /** |
|
362 | * Gets the dynamic configuration setting to be used for bean reading. |
|
363 | * @return the BindingConfiguration settings, not null |
|
364 | * @since 0.5 |
|
365 | */ |
|
366 | public BindingConfiguration getBindingConfiguration() { |
|
367 | 214 | return bindingConfiguration; |
368 | } |
|
369 | ||
370 | /** |
|
371 | * Sets the dynamic configuration setting to be used for bean reading. |
|
372 | * @param bindingConfiguration the BindingConfiguration settings, not null |
|
373 | * @since 0.5 |
|
374 | */ |
|
375 | public void setBindingConfiguration( BindingConfiguration bindingConfiguration ) { |
|
376 | 26 | this.bindingConfiguration = bindingConfiguration; |
377 | 26 | } |
378 | ||
379 | /** |
|
380 | * Gets read specific configuration details. |
|
381 | * @return the ReadConfiguration, not null |
|
382 | * @since 0.5 |
|
383 | */ |
|
384 | public ReadConfiguration getReadConfiguration() { |
|
385 | 26 | return readConfiguration; |
386 | } |
|
387 | ||
388 | /** |
|
389 | * Sets the read specific configuration details. |
|
390 | * @param readConfiguration not null |
|
391 | * @since 0.5 |
|
392 | */ |
|
393 | public void setReadConfiguration( ReadConfiguration readConfiguration ) { |
|
394 | 0 | this.readConfiguration = readConfiguration; |
395 | 0 | } |
396 | ||
397 | // Implementation methods |
|
398 | //------------------------------------------------------------------------- |
|
399 | ||
400 | /** |
|
401 | * Adds a new bean create rule for the specified path |
|
402 | * |
|
403 | * @param path the digester path at which this rule should be added |
|
404 | * @param elementDescriptor the <code>ElementDescriptor</code> describes the expected element |
|
405 | * @param beanClass the <code>Class</code> of the bean created by this rule |
|
406 | */ |
|
407 | protected void addBeanCreateRule( |
|
408 | String path, |
|
409 | ElementDescriptor elementDescriptor, |
|
410 | Class beanClass ) { |
|
411 | 1539 | if (log.isTraceEnabled()) { |
412 | 0 | log.trace("Adding BeanRuleSet for " + beanClass); |
413 | } |
|
414 | 3078 | RuleSet ruleSet = new BeanRuleSet( |
415 | 1539 | introspector, |
416 | 1539 | path , |
417 | 1539 | elementDescriptor, |
418 | 1539 | beanClass, |
419 | 1539 | makeContext()); |
420 | 1539 | addRuleSet( ruleSet ); |
421 | 1539 | } |
422 | ||
423 | /** |
|
424 | * Factory method for new contexts. |
|
425 | * Ensure that they are correctly configured. |
|
426 | * @return the ReadContext created, not null |
|
427 | */ |
|
428 | private ReadContext makeContext() { |
|
429 | 1539 | return new ReadContext( log, bindingConfiguration, readConfiguration ); |
430 | } |
|
431 | } |