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