KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > betwixt > io > BeanCreateRule


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.util.HashMap JavaDoc;
19 import java.util.List JavaDoc;
20 import java.util.Map JavaDoc;
21
22 import org.apache.commons.betwixt.AttributeDescriptor;
23 import org.apache.commons.betwixt.ElementDescriptor;
24 import org.apache.commons.betwixt.XMLBeanInfo;
25 import org.apache.commons.betwixt.XMLIntrospector;
26 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
27 import org.apache.commons.betwixt.expression.Context;
28 import org.apache.commons.betwixt.expression.MethodUpdater;
29 import org.apache.commons.betwixt.expression.Updater;
30 import org.apache.commons.digester.Rule;
31 import org.apache.commons.digester.Rules;
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.xml.sax.Attributes JavaDoc;
35
36 /** <p><code>BeanCreateRule</code> is a Digester Rule for creating beans
37   * from the betwixt XML metadata.</p>
38   *
39   * @author <a HREF="mailto:jstrachan@apache.org">James Strachan</a>
40   * @author <a HREF="mailto:martin@mvdb.net">Martin van den Bemt</a>
41   * @deprecated 0.5 this Rule does not allowed good integration with other Rules -
42   * use {@link BeanRuleSet} instead.
43   */

44 public class BeanCreateRule extends Rule {
45
46     /** Logger */
47     private static Log log = LogFactory.getLog( BeanCreateRule.class );
48     
49     /**
50      * Set log to be used by <code>BeanCreateRule</code> instances
51      * @param aLog the <code>Log</code> implementation for this class to log to
52      */

53     public static void setLog(Log aLog) {
54         log = aLog;
55     }
56     
57     /** The descriptor of this element */
58     private ElementDescriptor descriptor;
59     /** The Context used when evaluating Updaters */
60     private Context context;
61     /** Have we added our child rules to the digester? */
62     private boolean addedChildren;
63     /** In this begin-end loop did we actually create a new bean */
64     private boolean createdBean;
65     /** The type of the bean to create */
66     private Class JavaDoc beanClass;
67     /** The prefix added to digester rules */
68     private String JavaDoc pathPrefix;
69     /** Use id's to match beans? */
70     private boolean matchIDs = true;
71     /** allows an attribute to be specified to overload the types of beans used */
72     private String JavaDoc classNameAttribute = "className";
73     
74     /**
75      * Convenience constructor which uses <code>ID's</code> for matching.
76      *
77      * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
78      * @param beanClass the <code>Class</code> to be created
79      * @param pathPrefix the digester style path
80      */

81     public BeanCreateRule(
82                             ElementDescriptor descriptor,
83                             Class JavaDoc beanClass,
84                             String JavaDoc pathPrefix ) {
85         this( descriptor, beanClass, pathPrefix, true );
86     }
87     
88     /**
89      * Constructor taking a class.
90      *
91      * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
92      * @param beanClass the <code>Class</code> to be created
93      * @param pathPrefix the digester style path
94      * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching
95      */

96     public BeanCreateRule(
97                             ElementDescriptor descriptor,
98                             Class JavaDoc beanClass,
99                             String JavaDoc pathPrefix,
100                             boolean matchIDs ) {
101         this(
102                 descriptor,
103                 beanClass,
104                 new Context(),
105                 pathPrefix,
106                 matchIDs);
107     }
108     
109     /**
110      * Convenience constructor which uses <code>ID's</code> for matching.
111      *
112      * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
113      * @param beanClass the <code>Class</code> to be created
114      */

115     public BeanCreateRule( ElementDescriptor descriptor, Class JavaDoc beanClass ) {
116         this( descriptor, beanClass, true );
117     }
118     
119     /**
120      * Constructor uses standard qualified name.
121      *
122      * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
123      * @param beanClass the <code>Class</code> to be created
124      * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching
125      */

126     public BeanCreateRule( ElementDescriptor descriptor, Class JavaDoc beanClass, boolean matchIDs ) {
127         this( descriptor, beanClass, descriptor.getQualifiedName() + "/" , matchIDs );
128     }
129   
130     /**
131      * Convenience constructor which uses <code>ID's</code> for match.
132      *
133      * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
134      * @param context the <code>Context</code> to be used to evaluate expressions
135      * @param pathPrefix the digester path prefix
136      */

137     public BeanCreateRule(
138                             ElementDescriptor descriptor,
139                             Context context,
140                             String JavaDoc pathPrefix ) {
141         this( descriptor, context, pathPrefix, true );
142     }
143     
144     /**
145      * Constructor taking a context.
146      *
147      * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
148      * @param context the <code>Context</code> to be used to evaluate expressions
149      * @param pathPrefix the digester path prefix
150      * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching
151      */

152     public BeanCreateRule(
153                             ElementDescriptor descriptor,
154                             Context context,
155                             String JavaDoc pathPrefix,
156                             boolean matchIDs ) {
157         this(
158                 descriptor,
159                 descriptor.getSingularPropertyType(),
160                 context,
161                 pathPrefix,
162                 matchIDs );
163     }
164     
165     /**
166      * Base constructor (used by other constructors).
167      *
168      * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
169      * @param beanClass the <code>Class</code> of the bean to be created
170      * @param context the <code>Context</code> to be used to evaluate expressions
171      * @param pathPrefix the digester path prefix
172      * @param matchIDs should <code>ID</code>/<code>IDREF</code>'s be used for matching
173      */

174     private BeanCreateRule(
175                             ElementDescriptor descriptor,
176                             Class JavaDoc beanClass,
177                             Context context,
178                             String JavaDoc pathPrefix,
179                             boolean matchIDs ) {
180         this.descriptor = descriptor;
181         this.context = context;
182         this.beanClass = beanClass;
183         this.pathPrefix = pathPrefix;
184         this.matchIDs = matchIDs;
185         if (log.isTraceEnabled()) {
186             log.trace("Created bean create rule");
187             log.trace("Descriptor=" + descriptor);
188             log.trace("Class=" + beanClass);
189             log.trace("Path prefix=" + pathPrefix);
190         }
191     }
192     
193     
194         
195     // Rule interface
196
//-------------------------------------------------------------------------
197

198     /**
199      * Process the beginning of this element.
200      *
201      * @param attributes The attribute list of this element
202      */

203     public void begin(Attributes JavaDoc attributes) {
204         log.debug( "Called with descriptor: " + descriptor
205                     + " propertyType: " + descriptor.getPropertyType() );
206         
207         if (log.isTraceEnabled()) {
208             int attributesLength = attributes.getLength();
209             if (attributesLength > 0) {
210                 log.trace("Attributes:");
211             }
212             for (int i=0, size=attributesLength; i<size; i++) {
213                 log.trace("Local:" + attributes.getLocalName(i));
214                 log.trace("URI:" + attributes.getURI(i));
215                 log.trace("QName:" + attributes.getQName(i));
216             }
217         }
218         
219
220         
221         // XXX: if a single rule instance gets reused and nesting occurs
222
// XXX: we should probably use a stack of booleans to test if we created a bean
223
// XXX: or let digester take nulls, which would be easier for us ;-)
224
createdBean = false;
225                 
226         Object JavaDoc instance = null;
227         if ( beanClass != null ) {
228             instance = createBean(attributes);
229             if ( instance != null ) {
230                 createdBean = true;
231
232                 context.setBean( instance );
233                 digester.push(instance);
234                 
235         
236                 // if we are a reference to a type we should lookup the original
237
// as this ElementDescriptor will be 'hollow' and have no child attributes/elements.
238
// XXX: this should probably be done by the NodeDescriptors...
239
ElementDescriptor typeDescriptor = getElementDescriptor( descriptor );
240                 //ElementDescriptor typeDescriptor = descriptor;
241

242                 // iterate through all attributes
243
AttributeDescriptor[] attributeDescriptors
244                     = typeDescriptor.getAttributeDescriptors();
245                 if ( attributeDescriptors != null ) {
246                     for ( int i = 0, size = attributeDescriptors.length; i < size; i++ ) {
247                         AttributeDescriptor attributeDescriptor = attributeDescriptors[i];
248                         
249                         // The following isn't really the right way to find the attribute
250
// but it's quite robust.
251
// The idea is that you try both namespace and local name first
252
// and if this returns null try the qName.
253
String JavaDoc value = attributes.getValue(
254                             attributeDescriptor.getURI(),
255                             attributeDescriptor.getLocalName()
256                         );
257                         
258                         if (value == null) {
259                             value = attributes.getValue(attributeDescriptor.getQualifiedName());
260                         }
261                         
262                         if (log.isTraceEnabled()) {
263                             log.trace("Attr URL:" + attributeDescriptor.getURI());
264                             log.trace("Attr LocalName:" + attributeDescriptor.getLocalName() );
265                             log.trace(value);
266                         }
267                         
268                         Updater updater = attributeDescriptor.getUpdater();
269                         log.trace(updater);
270                         if ( updater != null && value != null ) {
271                             updater.update( context, value );
272                         }
273                     }
274                 }
275                 
276                 addChildRules();
277                 
278                 // add bean for ID matching
279
if ( matchIDs ) {
280                     // XXX need to support custom ID attribute names
281
// XXX i have a feeling that the current mechanism might need to change
282
// XXX so i'm leaving this till later
283
String JavaDoc id = attributes.getValue( "id" );
284                     if ( id != null ) {
285                         getBeansById().put( id, instance );
286                     }
287                 }
288             }
289         }
290     }
291
292     /**
293      * Process the end of this element.
294      */

295     public void end() {
296         if ( createdBean ) {
297             
298             // force any setters of the parent bean to be called for this new bean instance
299
Updater updater = descriptor.getUpdater();
300             Object JavaDoc instance = context.getBean();
301
302             Object JavaDoc top = digester.pop();
303             if (digester.getCount() == 0) {
304                 context.setBean(null);
305             }else{
306                 context.setBean( digester.peek() );
307             }
308
309             if ( updater != null ) {
310                 if ( log.isDebugEnabled() ) {
311                     log.debug( "Calling updater for: " + descriptor + " with: "
312                         + instance + " on bean: " + context.getBean() );
313                 }
314                 updater.update( context, instance );
315             } else {
316                 if ( log.isDebugEnabled() ) {
317                     log.debug( "No updater for: " + descriptor + " with: "
318                         + instance + " on bean: " + context.getBean() );
319                 }
320             }
321         }
322     }
323
324     /**
325      * Tidy up.
326      */

327     public void finish() {}
328
329
330     // Properties
331
//-------------------------------------------------------------------------
332

333
334     /**
335      * The name of the attribute which can be specified in the XML to override the
336      * type of a bean used at a certain point in the schema.
337      *
338      * <p>The default value is 'className'.</p>
339      *
340      * @return The name of the attribute used to overload the class name of a bean
341      */

342     public String JavaDoc getClassNameAttribute() {
343         return classNameAttribute;
344     }
345
346     /**
347      * Sets the name of the attribute which can be specified in
348      * the XML to override the type of a bean used at a certain
349      * point in the schema.
350      *
351      * <p>The default value is 'className'.</p>
352      *
353      * @param classNameAttribute The name of the attribute used to overload the class name of a bean
354      */

355     public void setClassNameAttribute(String JavaDoc classNameAttribute) {
356         this.classNameAttribute = classNameAttribute;
357     }
358
359     // Implementation methods
360
//-------------------------------------------------------------------------
361

362     /**
363      * Factory method to create new bean instances
364      *
365      * @param attributes the <code>Attributes</code> used to match <code>ID/IDREF</code>
366      * @return the created bean
367      */

368     protected Object JavaDoc createBean(Attributes JavaDoc attributes) {
369         //
370
// See if we've got an IDREF
371
//
372
// XXX This should be customizable but i'm not really convinced by the existing system
373
// XXX maybe it's going to have to change so i'll use 'idref' for nows
374
//
375
if ( matchIDs ) {
376             String JavaDoc idref = attributes.getValue( "idref" );
377             if ( idref != null ) {
378                 // XXX need to check up about ordering
379
// XXX this is a very simple system that assumes that id occurs before idrefs
380
// XXX would need some thought about how to implement a fuller system
381
log.trace( "Found IDREF" );
382                 Object JavaDoc bean = getBeansById().get( idref );
383                 if ( bean != null ) {
384                     if (log.isTraceEnabled()) {
385                         log.trace( "Matched bean " + bean );
386                     }
387                     return bean;
388                 }
389                 log.trace( "No match found" );
390             }
391         }
392         
393         Class JavaDoc theClass = beanClass;
394         try {
395             
396             String JavaDoc className = attributes.getValue(classNameAttribute);
397             if (className != null) {
398                 // load the class we should instantiate
399
theClass = getDigester().getClassLoader().loadClass(className);
400             }
401             if (log.isTraceEnabled()) {
402                 log.trace( "Creating instance of " + theClass );
403             }
404             return theClass.newInstance();
405             
406         } catch (Exception JavaDoc e) {
407             log.warn( "Could not create instance of type: " + theClass.getName() );
408             return null;
409         }
410     }
411         
412     /** Adds the rules to the digester for all child elements */
413     protected void addChildRules() {
414         if ( ! addedChildren ) {
415             addedChildren = true;
416             
417             addChildRules( pathPrefix, descriptor );
418         }
419     }
420                         
421     /**
422      * Add child rules for given descriptor at given prefix
423      *
424      * @param prefix add child rules at this (digester) path prefix
425      * @param currentDescriptor add child rules for this descriptor
426      */

427     protected void addChildRules(String JavaDoc prefix, ElementDescriptor currentDescriptor ) {
428         
429         if (log.isTraceEnabled()) {
430             log.trace("Adding child rules for " + currentDescriptor + "@" + prefix);
431         }
432         
433         // if we are a reference to a type we should lookup the original
434
// as this ElementDescriptor will be 'hollow' and have no child attributes/elements.
435
// XXX: this should probably be done by the NodeDescriptors...
436
ElementDescriptor typeDescriptor = getElementDescriptor( currentDescriptor );
437         //ElementDescriptor typeDescriptor = descriptor;
438

439         
440         ElementDescriptor[] childDescriptors = typeDescriptor.getElementDescriptors();
441         if ( childDescriptors != null ) {
442             for ( int i = 0, size = childDescriptors.length; i < size; i++ ) {
443                 final ElementDescriptor childDescriptor = childDescriptors[i];
444                 if (log.isTraceEnabled()) {
445                     log.trace("Processing child " + childDescriptor);
446                 }
447                 
448                 String JavaDoc qualifiedName = childDescriptor.getQualifiedName();
449                 if ( qualifiedName == null ) {
450                     log.trace( "Ignoring" );
451                     continue;
452                 }
453                 String JavaDoc path = prefix + qualifiedName;
454                 // this code is for making sure that recursive elements
455
// can also be used..
456

457                 if ( qualifiedName.equals( currentDescriptor.getQualifiedName() )
458                         && currentDescriptor.getPropertyName() != null ) {
459                     log.trace("Creating generic rule for recursive elements");
460                     int index = -1;
461                     if (childDescriptor.isWrapCollectionsInElement()) {
462                         index = prefix.indexOf(qualifiedName);
463                         if (index == -1) {
464                             // shouldn't happen..
465
log.debug( "Oops - this shouldn't happen" );
466                             continue;
467                         }
468                         int removeSlash = prefix.endsWith("/")?1:0;
469                         path = "*/" + prefix.substring(index, prefix.length()-removeSlash);
470                     }else{
471                         // we have a element/element type of thing..
472
ElementDescriptor[] desc = currentDescriptor.getElementDescriptors();
473                         if (desc.length == 1) {
474                             path = "*/"+desc[0].getQualifiedName();
475                         }
476                     }
477                     Rule rule = new BeanCreateRule( childDescriptor, context, path, matchIDs);
478                     addRule(path, rule);
479                     continue;
480                 }
481                 if ( childDescriptor.getUpdater() != null ) {
482                     if (log.isTraceEnabled()) {
483                         log.trace("Element has updater "
484                          + ((MethodUpdater) childDescriptor.getUpdater()).getMethod().getName());
485                     }
486                     if ( childDescriptor.isPrimitiveType() ) {
487                         addPrimitiveTypeRule(path, childDescriptor);
488                         
489                     } else {
490                         // add the first child to the path
491
ElementDescriptor[] grandChildren = childDescriptor.getElementDescriptors();
492                         if ( grandChildren != null && grandChildren.length > 0 ) {
493                             ElementDescriptor grandChild = grandChildren[0];
494                             String JavaDoc grandChildQName = grandChild.getQualifiedName();
495                             if ( grandChildQName != null && grandChildQName.length() > 0 ) {
496                                 if (childDescriptor.isWrapCollectionsInElement()) {
497                                     path += '/' + grandChildQName;
498                                     
499                                 } else {
500                                     path = prefix + (prefix.endsWith("/")?"":"/") + grandChildQName;
501                                 }
502                             }
503                         }
504                         
505                         // maybe we are adding a primitve type to a collection/array
506
Class JavaDoc beanClass = childDescriptor.getSingularPropertyType();
507                         if ( XMLIntrospectorHelper.isPrimitiveType( beanClass ) ) {
508                             addPrimitiveTypeRule(path, childDescriptor);
509                             
510                         } else {
511                             Rule rule = new BeanCreateRule(
512                                                         childDescriptor,
513                                                         context,
514                                                         path + '/',
515                                                         matchIDs );
516                             addRule( path, rule );
517                         }
518                     }
519                 } else {
520                     log.trace("Element does not have updater");
521                 }
522
523                 ElementDescriptor[] grandChildren = childDescriptor.getElementDescriptors();
524                 if ( grandChildren != null && grandChildren.length > 0 ) {
525                     log.trace("Adding grand children");
526                     addChildRules( path + '/', childDescriptor );
527                 }
528             }
529         }
530     }
531     
532     /**
533      * Get the associated bean reader.
534      *
535      * @return the <code>BeanReader</code digesting the xml
536      */

537     protected BeanReader getBeanReader() {
538         // XXX this breaks the rule contact
539
// XXX maybe the reader should be passed in the constructor
540
return (BeanReader) getDigester();
541     }
542     
543     /** Allows the navigation from a reference to a property object to the descriptor defining what
544      * the property is. i.e. doing the join from a reference to a type to lookup its descriptor.
545      * This could be done automatically by the NodeDescriptors. Refer to TODO.txt for more info.
546      *
547      * @param propertyDescriptor find descriptor for property object referenced by this descriptor
548      * @return descriptor for the singular property class type referenced.
549      */

550     protected ElementDescriptor getElementDescriptor( ElementDescriptor propertyDescriptor ) {
551         Class JavaDoc beanClass = propertyDescriptor.getSingularPropertyType();
552         if ( beanClass != null ) {
553             XMLIntrospector introspector = getBeanReader().getXMLIntrospector();
554             try {
555                 XMLBeanInfo xmlInfo = introspector.introspect( beanClass );
556                 return xmlInfo.getElementDescriptor();
557                 
558             } catch (Exception JavaDoc e) {
559                 log.warn( "Could not introspect class: " + beanClass, e );
560             }
561         }
562         // could not find a better descriptor so use the one we've got
563
return propertyDescriptor;
564     }
565     
566     /**
567      * Adds a new Digester rule to process the text as a primitive type
568      *
569      * @param path digester path where this rule will be attached
570      * @param childDescriptor update this <code>ElementDescriptor</code> with the body text
571      */

572     protected void addPrimitiveTypeRule(String JavaDoc path, final ElementDescriptor childDescriptor) {
573         Rule rule = new Rule() {
574             public void body(String JavaDoc text) throws Exception JavaDoc {
575                 childDescriptor.getUpdater().update( context, text );
576             }
577         };
578         addRule( path, rule );
579     }
580     
581     /**
582      * Safely add a rule with given path.
583      *
584      * @param path the digester path to add rule at
585      * @param rule the <code>Rule</code> to add
586      */

587     protected void addRule(String JavaDoc path, Rule rule) {
588         Rules rules = digester.getRules();
589         List JavaDoc matches = rules.match(null, path);
590         if ( matches.isEmpty() ) {
591             if ( log.isDebugEnabled() ) {
592                 log.debug( "Adding digester rule for path: " + path + " rule: " + rule );
593             }
594             digester.addRule( path, rule );
595             
596         } else {
597             if ( log.isDebugEnabled() ) {
598                 log.debug( "Ignoring duplicate digester rule for path: "
599                             + path + " rule: " + rule );
600                 log.debug( "New rule (not added): " + rule );
601                 log.debug( "Existing rule:" + matches.get(0) );
602             }
603         }
604     }
605
606     /**
607      * Get the map used to index beans (previously read in) by id.
608      * This is stored in the evaluation context.
609      *
610      * @return map indexing beans created by id
611      */

612     protected Map JavaDoc getBeansById() {
613         //
614
// we need a single index for beans read in by id
615
// so that we can use them for idref-matching
616
// store this in the context
617
//
618
Map JavaDoc beansById = (Map JavaDoc) context.getVariable( "beans-index" );
619         if ( beansById == null ) {
620             // lazy creation
621
beansById = new HashMap JavaDoc();
622             context.setVariable( "beans-index", beansById );
623             log.trace( "Created new index-by-id map" );
624         }
625         
626         return beansById;
627     }
628     
629     /**
630      * Return something meaningful for logging.
631      *
632      * @return something useful for logging
633      */

634     public String JavaDoc toString() {
635         return "BeanCreateRule [path prefix=" + pathPrefix + " descriptor=" + descriptor + "]";
636     }
637     
638 }
639
Popular Tags