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