View Javadoc

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      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      	// TODO: now we require extended rules may need to document this
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     // 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         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     // 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         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 }