1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 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 // TODO: now we require extended rules may need to document this 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 // Properties 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 // Implementation methods 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 }