Coverage Report - org.apache.commons.betwixt.io.BeanReader

Classes in this File Line Coverage Branch Coverage Complexity
BeanReader
67% 
77% 
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  
 }