View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/betwixt/src/java/org/apache/commons/betwixt/io/BeanCreateRule.java,v 1.17.2.1 2003/01/19 16:57:52 mvdb Exp $ 3 * $Revision: 1.17.2.1 $ 4 * $Date: 2003/01/19 16:57:52 $ 5 * 6 * ==================================================================== 7 * 8 * The Apache Software License, Version 1.1 9 * 10 * Copyright (c) 1999-2003 The Apache Software Foundation. All rights 11 * reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in 22 * the documentation and/or other materials provided with the 23 * distribution. 24 * 25 * 3. The end-user documentation included with the redistribution, if 26 * any, must include the following acknowlegement: 27 * "This product includes software developed by the 28 * Apache Software Foundation (http://www.apache.org/)." 29 * Alternately, this acknowlegement may appear in the software itself, 30 * if and wherever such third-party acknowlegements normally appear. 31 * 32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 33 * Foundation" must not be used to endorse or promote products derived 34 * from this software without prior written permission. For written 35 * permission, please contact apache@apache.org. 36 * 37 * 5. Products derived from this software may not be called "Apache" 38 * nor may "Apache" appear in their names without prior written 39 * permission of the Apache Group. 40 * 41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 52 * SUCH DAMAGE. 53 * ==================================================================== 54 * 55 * This software consists of voluntary contributions made by many 56 * individuals on behalf of the Apache Software Foundation. For more 57 * information on the Apache Software Foundation, please see 58 * <http://www.apache.org/>;. 59 * 60 * $Id: BeanCreateRule.java,v 1.17.2.1 2003/01/19 16:57:52 mvdb Exp $ 61 */ 62 package org.apache.commons.betwixt.io; 63 64 import java.util.HashMap; 65 import java.util.List; 66 import java.util.Map; 67 68 import org.apache.commons.betwixt.AttributeDescriptor; 69 import org.apache.commons.betwixt.ElementDescriptor; 70 import org.apache.commons.betwixt.XMLBeanInfo; 71 import org.apache.commons.betwixt.XMLIntrospector; 72 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper; 73 import org.apache.commons.betwixt.expression.Context; 74 import org.apache.commons.betwixt.expression.MethodUpdater; 75 import org.apache.commons.betwixt.expression.Updater; 76 import org.apache.commons.digester.Rule; 77 import org.apache.commons.digester.Rules; 78 import org.apache.commons.logging.Log; 79 import org.apache.commons.logging.LogFactory; 80 import org.xml.sax.Attributes; 81 82 /*** <p><code>BeanCreateRule</code> is a Digester Rule for creating beans 83 * from the betwixt XML metadata.</p> 84 * 85 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 86 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> 87 * @version $Revision: 1.17.2.1 $ 88 */ 89 public class BeanCreateRule extends Rule { 90 91 /*** Logger */ 92 private static Log log = LogFactory.getLog( BeanCreateRule.class ); 93 94 /*** 95 * Set log to be used by <code>BeanCreateRule</code> instances 96 * @param aLog the <code>Log</code> implementation for this class to log to 97 */ 98 public static void setLog(Log aLog) { 99 log = aLog; 100 } 101 102 /*** The descriptor of this element */ 103 private ElementDescriptor descriptor; 104 /*** The Context used when evaluating Updaters */ 105 private Context context; 106 /*** Have we added our child rules to the digester? */ 107 private boolean addedChildren; 108 /*** In this begin-end loop did we actually create a new bean */ 109 private boolean createdBean; 110 /*** The type of the bean to create */ 111 private Class beanClass; 112 /*** The prefix added to digester rules */ 113 private String pathPrefix; 114 /*** Use id's to match beans? */ 115 private boolean matchIDs = true; 116 117 /*** 118 * Convenience constructor which uses <code>ID's</code> for matching. 119 * 120 * @param descriptor the <code>ElementDescriptor</code> describing the element mapped 121 * @param beanClass the <code>Class</code> to be created 122 * @param pathPrefix the digester style path 123 */ 124 public BeanCreateRule( 125 ElementDescriptor descriptor, 126 Class beanClass, 127 String pathPrefix ) { 128 this( descriptor, beanClass, pathPrefix, true ); 129 } 130 131 /*** 132 * Constructor taking a class. 133 * 134 * @param descriptor the <code>ElementDescriptor</code> describing the element mapped 135 * @param beanClass the <code>Class</code> to be created 136 * @param pathPrefix the digester style path 137 * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching 138 */ 139 public BeanCreateRule( 140 ElementDescriptor descriptor, 141 Class beanClass, 142 String pathPrefix, 143 boolean matchIDs ) { 144 this( 145 descriptor, 146 beanClass, 147 new Context(), 148 pathPrefix, 149 matchIDs); 150 } 151 152 /*** 153 * Convenience constructor which uses <code>ID's</code> for matching. 154 * 155 * @param descriptor the <code>ElementDescriptor</code> describing the element mapped 156 * @param beanClass the <code>Class</code> to be created 157 */ 158 public BeanCreateRule( ElementDescriptor descriptor, Class beanClass ) { 159 this( descriptor, beanClass, true ); 160 } 161 162 /*** 163 * Constructor uses standard qualified name. 164 * 165 * @param descriptor the <code>ElementDescriptor</code> describing the element mapped 166 * @param beanClass the <code>Class</code> to be created 167 * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching 168 */ 169 public BeanCreateRule( ElementDescriptor descriptor, Class beanClass, boolean matchIDs ) { 170 this( descriptor, beanClass, descriptor.getQualifiedName() + "/" , matchIDs ); 171 } 172 173 /*** 174 * Convenience constructor which uses <code>ID's</code> for match. 175 * 176 * @param descriptor the <code>ElementDescriptor</code> describing the element mapped 177 * @param context the <code>Context</code> to be used to evaluate expressions 178 * @param pathPrefix the digester path prefix 179 */ 180 public BeanCreateRule( 181 ElementDescriptor descriptor, 182 Context context, 183 String pathPrefix ) { 184 this( descriptor, context, pathPrefix, true ); 185 } 186 187 /*** 188 * Constructor taking a context. 189 * 190 * @param descriptor the <code>ElementDescriptor</code> describing the element mapped 191 * @param context the <code>Context</code> to be used to evaluate expressions 192 * @param pathPrefix the digester path prefix 193 * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching 194 */ 195 public BeanCreateRule( 196 ElementDescriptor descriptor, 197 Context context, 198 String pathPrefix, 199 boolean matchIDs ) { 200 this( 201 descriptor, 202 descriptor.getSingularPropertyType(), 203 context, 204 pathPrefix, 205 matchIDs ); 206 } 207 208 /*** 209 * Base constructor (used by other constructors). 210 * 211 * @param descriptor the <code>ElementDescriptor</code> describing the element mapped 212 * @param beanClass the <code>Class</code> of the bean to be created 213 * @param context the <code>Context</code> to be used to evaluate expressions 214 * @param pathPrefix the digester path prefix 215 * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching 216 */ 217 private BeanCreateRule( 218 ElementDescriptor descriptor, 219 Class beanClass, 220 Context context, 221 String pathPrefix, 222 boolean matchIDs ) { 223 this.descriptor = descriptor; 224 this.context = context; 225 this.beanClass = beanClass; 226 this.pathPrefix = pathPrefix; 227 this.matchIDs = matchIDs; 228 if (log.isTraceEnabled()) { 229 log.trace("Created bean create rule"); 230 log.trace("Descriptor=" + descriptor); 231 log.trace("Class=" + beanClass); 232 log.trace("Path prefix=" + pathPrefix); 233 } 234 } 235 236 237 238 // Rule interface 239 //------------------------------------------------------------------------- 240 241 /*** 242 * Process the beginning of this element. 243 * 244 * @param attributes The attribute list of this element 245 */ 246 public void begin(Attributes attributes) { 247 log.debug( "Called with descriptor: " + descriptor 248 + " propertyType: " + descriptor.getPropertyType() ); 249 250 if (log.isTraceEnabled()) { 251 int attributesLength = attributes.getLength(); 252 if (attributesLength > 0) { 253 log.trace("Attributes:"); 254 } 255 for (int i=0, size=attributesLength; i<size; i++) { 256 log.trace("Local:" + attributes.getLocalName(i)); 257 log.trace("URI:" + attributes.getURI(i)); 258 log.trace("QName:" + attributes.getQName(i)); 259 } 260 } 261 262 263 264 // XXX: if a single rule instance gets reused and nesting occurs 265 // XXX: we should probably use a stack of booleans to test if we created a bean 266 // XXX: or let digester take nulls, which would be easier for us ;-) 267 createdBean = false; 268 269 Object instance = null; 270 if ( beanClass != null ) { 271 instance = createBean(attributes); 272 if ( instance != null ) { 273 createdBean = true; 274 275 context.setBean( instance ); 276 digester.push(instance); 277 278 279 // if we are a reference to a type we should lookup the original 280 // as this ElementDescriptor will be 'hollow' and have no child attributes/elements. 281 // XXX: this should probably be done by the NodeDescriptors... 282 ElementDescriptor typeDescriptor = getElementDescriptor( descriptor ); 283 //ElementDescriptor typeDescriptor = descriptor; 284 285 // iterate through all attributes 286 AttributeDescriptor[] attributeDescriptors 287 = typeDescriptor.getAttributeDescriptors(); 288 if ( attributeDescriptors != null ) { 289 for ( int i = 0, size = attributeDescriptors.length; i < size; i++ ) { 290 AttributeDescriptor attributeDescriptor = attributeDescriptors[i]; 291 292 // The following isn't really the right way to find the attribute 293 // but it's quite robust. 294 // The idea is that you try both namespace and local name first 295 // and if this returns null try the qName. 296 String value = attributes.getValue( 297 attributeDescriptor.getURI(), 298 attributeDescriptor.getLocalName() 299 ); 300 301 if (value == null) { 302 value = attributes.getValue(attributeDescriptor.getQualifiedName()); 303 } 304 305 if (log.isTraceEnabled()) { 306 log.trace("Attr URL:" + attributeDescriptor.getURI()); 307 log.trace("Attr LocalName:" + attributeDescriptor.getLocalName() ); 308 log.trace(value); 309 } 310 311 Updater updater = attributeDescriptor.getUpdater(); 312 log.trace(updater); 313 if ( updater != null && value != null ) { 314 updater.update( context, value ); 315 } 316 } 317 } 318 319 addChildRules(); 320 321 // add bean for ID matching 322 if ( matchIDs ) { 323 // XXX need to support custom ID attribute names 324 // XXX i have a feeling that the current mechanism might need to change 325 // XXX so i'm leaving this till later 326 String id = attributes.getValue( "id" ); 327 if ( id != null ) { 328 getBeansById().put( id, instance ); 329 } 330 } 331 } 332 } 333 } 334 335 /*** 336 * Process the end of this element. 337 */ 338 public void end() { 339 if ( createdBean ) { 340 341 // force any setters of the parent bean to be called for this new bean instance 342 Updater updater = descriptor.getUpdater(); 343 Object instance = context.getBean(); 344 345 Object top = digester.pop(); 346 if (digester.getCount() == 0) { 347 context.setBean(null); 348 }else{ 349 context.setBean( digester.peek() ); 350 } 351 352 if ( updater != null ) { 353 if ( log.isDebugEnabled() ) { 354 log.debug( "Calling updater for: " + descriptor + " with: " 355 + instance + " on bean: " + context.getBean() ); 356 } 357 updater.update( context, instance ); 358 } else { 359 if ( log.isDebugEnabled() ) { 360 log.debug( "No updater for: " + descriptor + " with: " 361 + instance + " on bean: " + context.getBean() ); 362 } 363 } 364 } 365 } 366 367 /*** 368 * Tidy up. 369 */ 370 public void finish() {} 371 372 373 // Implementation methods 374 //------------------------------------------------------------------------- 375 376 /*** 377 * Factory method to create new bean instances 378 * 379 * @param attributes the <code>Attributes</code> used to match <code>ID/IDREF</code> 380 * @return the created bean 381 */ 382 protected Object createBean(Attributes attributes) { 383 // 384 // See if we've got an IDREF 385 // 386 // XXX This should be customizable but i'm not really convinced by the existing system 387 // XXX maybe it's going to have to change so i'll use 'idref' for nows 388 // 389 if ( matchIDs ) { 390 String idref = attributes.getValue( "idref" ); 391 if ( idref != null ) { 392 // XXX need to check up about ordering 393 // XXX this is a very simple system that assumes that id occurs before idrefs 394 // XXX would need some thought about how to implement a fuller system 395 log.trace( "Found IDREF" ); 396 Object bean = getBeansById().get( idref ); 397 if ( bean != null ) { 398 if (log.isTraceEnabled()) { 399 log.trace( "Matched bean " + bean ); 400 } 401 return bean; 402 } 403 log.trace( "No match found" ); 404 } 405 } 406 407 try { 408 if (log.isTraceEnabled()) { 409 log.trace( "Creating instance of " + beanClass ); 410 } 411 return beanClass.newInstance(); 412 413 } catch (Exception e) { 414 log.warn( "Could not create instance of type: " + beanClass.getName() ); 415 return null; 416 } 417 } 418 419 /*** Adds the rules to the digester for all child elements */ 420 protected void addChildRules() { 421 if ( ! addedChildren ) { 422 addedChildren = true; 423 424 addChildRules( pathPrefix, descriptor ); 425 } 426 } 427 428 /*** 429 * Add child rules for given descriptor at given prefix 430 * 431 * @param prefix add child rules at this (digester) path prefix 432 * @param currentDescriptor add child rules for this descriptor 433 */ 434 protected void addChildRules(String prefix, ElementDescriptor currentDescriptor ) { 435 BeanReader digester = getBeanReader(); 436 437 if (log.isTraceEnabled()) { 438 log.trace("Adding child rules for " + currentDescriptor + "@" + prefix); 439 } 440 441 // if we are a reference to a type we should lookup the original 442 // as this ElementDescriptor will be 'hollow' and have no child attributes/elements. 443 // XXX: this should probably be done by the NodeDescriptors... 444 ElementDescriptor typeDescriptor = getElementDescriptor( currentDescriptor ); 445 //ElementDescriptor typeDescriptor = descriptor; 446 447 448 ElementDescriptor[] childDescriptors = typeDescriptor.getElementDescriptors(); 449 if ( childDescriptors != null ) { 450 for ( int i = 0, size = childDescriptors.length; i < size; i++ ) { 451 final ElementDescriptor childDescriptor = childDescriptors[i]; 452 if (log.isTraceEnabled()) { 453 log.trace("Processing child " + childDescriptor); 454 } 455 456 String propertyName = childDescriptor.getPropertyName(); 457 String qualifiedName = childDescriptor.getQualifiedName(); 458 if ( qualifiedName == null ) { 459 log.trace( "Ignoring" ); 460 continue; 461 } 462 String path = prefix + qualifiedName; 463 // this code is for making sure that recursive elements 464 // can also be used.. 465 466 if ( qualifiedName.equals( currentDescriptor.getQualifiedName() ) 467 && currentDescriptor.getPropertyName() != null ) { 468 log.trace("Creating generic rule for recursive elements"); 469 int index = -1; 470 if (childDescriptor.isWrapCollectionsInElement()) { 471 index = prefix.indexOf(qualifiedName); 472 if (index == -1) { 473 // shouldn't happen.. 474 log.debug( "Oops - this shouldn't happen" ); 475 continue; 476 } 477 int removeSlash = prefix.endsWith("/")?1:0; 478 path = "*/" + prefix.substring(index, prefix.length()-removeSlash); 479 }else{ 480 // we have a element/element type of thing.. 481 ElementDescriptor[] desc = currentDescriptor.getElementDescriptors(); 482 if (desc.length == 1) { 483 path = "*/"+desc[0].getQualifiedName(); 484 } 485 } 486 Rule rule = new BeanCreateRule( childDescriptor, context, path, matchIDs); 487 addRule(path, rule); 488 continue; 489 } 490 if ( childDescriptor.getUpdater() != null ) { 491 if (log.isTraceEnabled()) { 492 log.trace("Element has updater " 493 + ((MethodUpdater) childDescriptor.getUpdater()).getMethod().getName()); 494 } 495 if ( childDescriptor.isPrimitiveType() ) { 496 addPrimitiveTypeRule(path, childDescriptor); 497 498 } else { 499 // add the first child to the path 500 ElementDescriptor[] grandChildren = childDescriptor.getElementDescriptors(); 501 if ( grandChildren != null && grandChildren.length > 0 ) { 502 ElementDescriptor grandChild = grandChildren[0]; 503 String grandChildQName = grandChild.getQualifiedName(); 504 if ( grandChildQName != null && grandChildQName.length() > 0 ) { 505 if (childDescriptor.isWrapCollectionsInElement()) { 506 path += '/' + grandChildQName; 507 508 } else { 509 path = prefix + (prefix.endsWith("/")?"":"/") + grandChildQName; 510 } 511 } 512 } 513 514 // maybe we are adding a primitve type to a collection/array 515 Class beanClass = childDescriptor.getSingularPropertyType(); 516 if ( XMLIntrospectorHelper.isPrimitiveType( beanClass ) ) { 517 addPrimitiveTypeRule(path, childDescriptor); 518 519 } else { 520 Rule rule = new BeanCreateRule( 521 childDescriptor, 522 context, 523 path + '/', 524 matchIDs ); 525 addRule( path, rule ); 526 } 527 } 528 } else { 529 log.trace("Element does not have updater"); 530 } 531 532 ElementDescriptor[] grandChildren = childDescriptor.getElementDescriptors(); 533 if ( grandChildren != null && grandChildren.length > 0 ) { 534 log.trace("Adding grand children"); 535 addChildRules( path + '/', childDescriptor ); 536 } 537 } 538 } 539 } 540 541 /*** 542 * Get the associated bean reader. 543 * 544 * @return the <code>BeanReader</code digesting the xml 545 */ 546 protected BeanReader getBeanReader() { 547 // XXX this breaks the rule contact 548 // XXX maybe the reader should be passed in the constructor 549 return (BeanReader) getDigester(); 550 } 551 552 /*** Allows the navigation from a reference to a property object to the descriptor defining what 553 * the property is. i.e. doing the join from a reference to a type to lookup its descriptor. 554 * This could be done automatically by the NodeDescriptors. Refer to TODO.txt for more info. 555 * 556 * @param propertyDescriptor find descriptor for property object referenced by this descriptor 557 * @return descriptor for the singular property class type referenced. 558 */ 559 protected ElementDescriptor getElementDescriptor( ElementDescriptor propertyDescriptor ) { 560 Class beanClass = propertyDescriptor.getSingularPropertyType(); 561 if ( beanClass != null ) { 562 XMLIntrospector introspector = getBeanReader().getXMLIntrospector(); 563 try { 564 XMLBeanInfo xmlInfo = introspector.introspect( beanClass ); 565 return xmlInfo.getElementDescriptor(); 566 567 } catch (Exception e) { 568 log.warn( "Could not introspect class: " + beanClass, e ); 569 } 570 } 571 // could not find a better descriptor so use the one we've got 572 return propertyDescriptor; 573 } 574 575 /*** 576 * Adds a new Digester rule to process the text as a primitive type 577 * 578 * @param path digester path where this rule will be attached 579 * @param childDescriptor update this <code>ElementDescriptor</code> with the body text 580 */ 581 protected void addPrimitiveTypeRule(String path, final ElementDescriptor childDescriptor) { 582 Rule rule = new Rule() { 583 public void body(String text) throws Exception { 584 childDescriptor.getUpdater().update( context, text ); 585 } 586 }; 587 addRule( path, rule ); 588 } 589 590 /*** 591 * Safely add a rule with given path. 592 * 593 * @param path the digester path to add rule at 594 * @param rule the <code>Rule</code> to add 595 */ 596 protected void addRule(String path, Rule rule) { 597 Rules rules = digester.getRules(); 598 List matches = rules.match(null, path); 599 if ( matches.isEmpty() ) { 600 if ( log.isDebugEnabled() ) { 601 log.debug( "Adding digester rule for path: " + path + " rule: " + rule ); 602 } 603 digester.addRule( path, rule ); 604 605 } else { 606 if ( log.isDebugEnabled() ) { 607 log.debug( "Ignoring duplicate digester rule for path: " 608 + path + " rule: " + rule ); 609 log.debug( "New rule (not added): " + rule ); 610 log.debug( "Existing rule:" + matches.get(0) ); 611 } 612 } 613 } 614 615 /*** 616 * Get the map used to index beans (previously read in) by id. 617 * This is stored in the evaluation context. 618 * 619 * @return map indexing beans created by id 620 */ 621 protected Map getBeansById() { 622 // 623 // we need a single index for beans read in by id 624 // so that we can use them for idref-matching 625 // store this in the context 626 // 627 Map beansById = (Map) context.getVariable( "beans-index" ); 628 if ( beansById == null ) { 629 // lazy creation 630 beansById = new HashMap(); 631 context.setVariable( "beans-index", beansById ); 632 log.trace( "Created new index-by-id map" ); 633 } 634 635 return beansById; 636 } 637 638 /*** 639 * Return something meaningful for logging. 640 * 641 * @return something useful for logging 642 */ 643 public String toString() { 644 return "BeanCreateRule [path prefix=" + pathPrefix + " descriptor=" + descriptor + "]"; 645 } 646 647 }

This page was automatically generated by Maven