1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 private XMLIntrospector introspector = new XMLIntrospector();
51 /*** Log used for logging (Doh!) */
52 private Log log = LogFactory.getLog( BeanReader.class );
53 /*** The registered classes */
54 private Set registeredClasses = new HashSet();
55 /*** Dynamic binding configuration settings */
56 private BindingConfiguration bindingConfiguration = new BindingConfiguration();
57 /*** Reading specific configuration settings */
58 private ReadConfiguration readConfiguration = new ReadConfiguration();
59
60 /***
61 * Construct a new BeanReader with default properties.
62 */
63 public BeanReader() {
64
65 setRules(new ExtendedBaseRules());
66 }
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 super(parser);
79 setRules(new ExtendedBaseRules());
80 }
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 super(reader);
93 setRules(new ExtendedBaseRules());
94 }
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 if ( ! registeredClasses.contains( beanClass ) ) {
121 register(beanClass, null);
122
123 } else {
124 if ( log.isWarnEnabled() ) {
125 log.warn("Cannot add class " + beanClass.getName() + " since it already exists");
126 }
127 }
128 }
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 if ( log.isTraceEnabled() ) {
139 log.trace( "Registering class " + beanClass );
140 }
141 XMLBeanInfo xmlInfo = introspector.introspect( beanClass );
142 registeredClasses.add( beanClass );
143
144 ElementDescriptor elementDescriptor = xmlInfo.getElementDescriptor();
145
146 if (path == null) {
147 path = elementDescriptor.getQualifiedName();
148 }
149
150 if (log.isTraceEnabled()) {
151 log.trace("Added path: " + path + ", mapped to: " + beanClass.getName());
152 }
153 addBeanCreateRule( path, elementDescriptor, beanClass );
154 }
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 if ( ! registeredClasses.contains( beanClass ) ) {
184
185 register(beanClass, path);
186
187 } else {
188 if ( log.isWarnEnabled() ) {
189 log.warn("Cannot add class " + beanClass.getName() + " since it already exists");
190 }
191 }
192 }
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 Class[] mappedClasses = introspector.register(mapping);
216 for (int i=0, size=mappedClasses.length; i<size; i++)
217 {
218 Class beanClass = mappedClasses[i];
219 if ( ! registeredClasses.contains( beanClass ) ) {
220 register(beanClass, null);
221
222 }
223 }
224 }
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 if ( ! registeredClasses.contains( beanClass ) ) {
248
249 introspector.register( beanClass, mapping );
250 register(beanClass, null);
251
252 } else {
253 if ( log.isWarnEnabled() ) {
254 log.warn("Cannot add class " + beanClass.getName() + " since it already exists");
255 }
256 }
257 }
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 registeredClasses.clear();
270 }
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 registeredClasses.remove( beanClass );
287 }
288
289
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 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 this.introspector = introspector;
319 }
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 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 this.log = log;
337 setLogger(log);
338 }
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 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 getBindingConfiguration().setMapIDs( matchIDs );
359 }
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 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 this.bindingConfiguration = bindingConfiguration;
377 }
378
379 /***
380 * Gets read specific configuration details.
381 * @return the ReadConfiguration, not null
382 * @since 0.5
383 */
384 public ReadConfiguration getReadConfiguration() {
385 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 this.readConfiguration = readConfiguration;
395 }
396
397
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 if (log.isTraceEnabled()) {
412 log.trace("Adding BeanRuleSet for " + beanClass);
413 }
414 RuleSet ruleSet = new BeanRuleSet(
415 introspector,
416 path ,
417 elementDescriptor,
418 beanClass,
419 makeContext());
420 addRuleSet( ruleSet );
421 }
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 return new ReadContext( log, bindingConfiguration, readConfiguration );
430 }
431 }