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.util.HashMap;
19  import java.util.Iterator;
20  import java.util.Map;
21  
22  import org.apache.commons.betwixt.AttributeDescriptor;
23  import org.apache.commons.betwixt.BindingConfiguration;
24  import org.apache.commons.betwixt.ElementDescriptor;
25  import org.apache.commons.betwixt.TextDescriptor;
26  import org.apache.commons.betwixt.XMLBeanInfo;
27  import org.apache.commons.betwixt.XMLIntrospector;
28  import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
29  import org.apache.commons.betwixt.expression.Context;
30  import org.apache.commons.betwixt.expression.MethodUpdater;
31  import org.apache.commons.betwixt.expression.Updater;
32  import org.apache.commons.betwixt.io.read.ElementMapping;
33  import org.apache.commons.betwixt.io.read.ReadConfiguration;
34  import org.apache.commons.betwixt.io.read.ReadContext;
35  import org.apache.commons.digester.Digester;
36  import org.apache.commons.digester.Rule;
37  import org.apache.commons.digester.RuleSet;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.xml.sax.Attributes;
41  
42  /*** <p>Sets <code>Betwixt</code> digestion rules for a bean class.</p>
43    *
44    * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
45    * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
46    * @since 0.5
47    */
48  public class BeanRuleSet implements RuleSet {
49      
50      
51      /*** Logger */
52      private static Log log = LogFactory.getLog( BeanRuleSet.class );
53      
54      /*** 
55      * Set log to be used by <code>BeanRuleSet</code> instances 
56      * @param aLog the <code>Log</code> implementation for this class to log to
57      */
58      public static void setLog(Log aLog) {
59          log = aLog;
60      }
61      
62      /*** Use this to introspect beans */
63      private XMLIntrospector introspector;
64      /*** The base path under which the rules will be attached */
65      private String basePath;
66      /*** The element descriptor for the base  */
67      private ElementDescriptor baseElementDescriptor;
68      /*** The bean based  */
69      private Class baseBeanClass;
70      /*** The (empty) base context from which all Contexts 
71      with beans are (directly or indirectly) obtained */
72      private ReadContext baseContext;
73      /*** allows an attribute to be specified to overload the types of beans used */
74      private String classNameAttribute = "className";
75      
76      /***
77       * Base constructor.
78       *
79       * @param introspector the <code>XMLIntrospector</code> used to introspect 
80       * @param basePath specifies the (Digester-style) path under which the rules will be attached
81       * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
82       * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
83       * @param matchIDs should ID/IDREFs be used to match beans?
84       * @deprecated 0.5 use constructor which takes a ReadContext instead
85       */
86      public BeanRuleSet(
87                          XMLIntrospector introspector,
88                          String basePath, 
89                          ElementDescriptor baseElementDescriptor, 
90                          Class baseBeanClass,
91                          boolean matchIDs) {
92          this.introspector = introspector;
93          this.basePath = basePath;
94          this.baseElementDescriptor = baseElementDescriptor;
95          this.baseBeanClass = baseBeanClass;
96          BindingConfiguration bindingConfiguration = new BindingConfiguration();
97          bindingConfiguration.setMapIDs( matchIDs );
98          baseContext = new ReadContext( log , bindingConfiguration, new ReadConfiguration() );
99      }
100     
101     /***
102      * Base constructor.
103      *
104      * @param introspector the <code>XMLIntrospector</code> used to introspect 
105      * @param basePath specifies the (Digester-style) path under which the rules will be attached
106      * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
107      * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
108      * @param context the root Context that bean carrying Contexts should be obtained from, 
109      * not null
110      * @deprecated 0.5 use the constructor which takes a ReadContext instead
111      */
112     public BeanRuleSet(
113                         XMLIntrospector introspector,
114                         String basePath, 
115                         ElementDescriptor baseElementDescriptor, 
116                         Class baseBeanClass,
117                         Context context) {
118         this.introspector = introspector;
119         this.basePath = basePath;
120         this.baseElementDescriptor = baseElementDescriptor;
121         this.baseBeanClass = baseBeanClass;
122         this.baseContext = new ReadContext( context, new ReadConfiguration() );
123     }
124     
125     /***
126      * Base constructor.
127      *
128      * @param introspector the <code>XMLIntrospector</code> used to introspect 
129      * @param basePath specifies the (Digester-style) path under which the rules will be attached
130      * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
131      * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
132      * @param baseContext the root Context that bean carrying Contexts should be obtained from, 
133      * not null
134      */
135     public BeanRuleSet(
136                         XMLIntrospector introspector,
137                         String basePath, 
138                         ElementDescriptor baseElementDescriptor, 
139                         Class baseBeanClass,
140                         ReadContext baseContext) {
141         this.introspector = introspector;
142         this.basePath = basePath;
143         this.baseElementDescriptor = baseElementDescriptor;
144         this.baseBeanClass = baseBeanClass;
145         this.baseContext = baseContext;
146     }
147 
148     /***
149      * The name of the attribute which can be specified in the XML to override the
150      * type of a bean used at a certain point in the schema.
151      *
152      * <p>The default value is 'className'.</p>
153      * 
154      * @return The name of the attribute used to overload the class name of a bean
155      */
156     public String getClassNameAttribute() {
157         return baseContext.getClassNameAttribute();
158     }
159 
160     /***
161      * Sets the name of the attribute which can be specified in 
162      * the XML to override the type of a bean used at a certain 
163      * point in the schema.
164      *
165      * <p>The default value is 'className'.</p>
166      * 
167      * @param classNameAttribute The name of the attribute used to overload the class name of a bean
168      * @deprecated 0.5 set the <code>ReadContext</code> property instead
169      */
170     public void setClassNameAttribute(String classNameAttribute) {
171         baseContext.setClassNameAttribute(classNameAttribute);
172     }
173     
174 //-------------------------------- Ruleset implementation
175 
176     /*** 
177      * <p>Return namespace associated with this ruleset</p>
178      *
179      * <p><strong>Note</strong> namespaces are not currently supported.</p>
180      * 
181      * @return null
182      */
183     public String getNamespaceURI() {
184         return null;
185     }
186     
187     /***
188      * Add rules for bean to given <code>Digester</code>.
189      *
190      * @param digester the <code>Digester</code> to which the rules for the bean will be added
191      */
192     public void addRuleInstances(Digester digester) {
193         if (log.isTraceEnabled()) {
194             log.trace("Adding rules to:" + digester);
195         }
196         
197         ReadingContext readContext = new ReadingContext( digester );
198     }
199     
200     /***
201      * <p>A set of associated rules that maps a bean graph.
202      * An instance will be created each time {@link #addRuleInstances} is called.</p>
203      *
204      * <p>When an instance is constructed, rules are created and added to digester.</p>
205      */
206     private class ReadingContext {
207 
208         /*** The rules in this context indexed by path */
209         private Map rulesByPath = new HashMap();
210         
211         /*** 
212          * Creates rules for bean and adds them to digester 
213          * @param digester the <code>Digester</code> 
214          * to which the bean mapping rules will be added
215          */
216         ReadingContext(Digester digester) {
217             ReadContext context = new ReadContext( baseContext );
218             // if the classloader is not set, set to the digester classloader
219             if ( context.getClassLoader() == null ) {
220                 context.setClassLoader( digester.getClassLoader()  );
221             }
222             BeanRule rule 
223                 = new BeanRule( basePath + "/" , baseElementDescriptor, baseBeanClass, context );
224             addRule( basePath, rule , baseElementDescriptor, context );
225             
226             if ( log.isDebugEnabled() ) {
227                 log.debug( "Added root rule to path: " + basePath + " class: " + baseBeanClass );
228             } 
229             
230             
231             Iterator it = rulesByPath.entrySet().iterator();
232             while (it.hasNext()) {
233                 Map.Entry entry = (Map.Entry) it.next();
234                 if ( log.isTraceEnabled() ) {
235                     log.trace("Added rule:" + entry.getValue() + " @path:" + entry.getKey());
236                 }
237                 digester.addRule( (String) entry.getKey() , (Rule) entry.getValue() );
238             }
239         }
240                                                                     
241         /*** 
242         * Add child rules for given descriptor at given prefix 
243         *
244         * @param prefix add child rules at this (digester) path prefix
245         * @param currentDescriptor add child rules for this descriptor
246         * @param context the <code>Context</code> against which beans will be evaluated 
247         */
248         private void addChildRules( 
249                                     String prefix, 
250                                     ElementDescriptor currentDescriptor, 
251                                     ReadContext context ) {
252             
253             if (log.isTraceEnabled()) {
254                 log.trace("Adding child rules for " + currentDescriptor + "@" + prefix);
255             }
256             
257             // if we are a reference to a type we should lookup the original
258             // as this ElementDescriptor will be 'hollow' and have no child attributes/elements.
259             // XXX: this should probably be done by the NodeDescriptors...
260             ElementDescriptor typeDescriptor = getElementDescriptor( currentDescriptor );
261             //ElementDescriptor typeDescriptor = descriptor;
262     
263             
264             ElementDescriptor[] childDescriptors = typeDescriptor.getElementDescriptors();
265             if ( childDescriptors != null ) {
266                 for ( int i = 0, size = childDescriptors.length; i < size; i++ ) {
267                     final ElementDescriptor childDescriptor = childDescriptors[i];
268                     if (log.isTraceEnabled()) {
269                         log.trace("Processing child " + childDescriptor);
270                     }
271                     
272                     String qualifiedName = childDescriptor.getQualifiedName();
273                     if ( qualifiedName == null ) {
274                         log.trace( "Ignoring" );
275                         continue;
276                     }
277                     String path = prefix + qualifiedName;
278                     // this code is for making sure that recursive elements
279                     // can also be used..
280                     
281                     if ( qualifiedName.equals( currentDescriptor.getQualifiedName() ) 
282                             && currentDescriptor.getPropertyName() != null ) {
283                         log.trace("Creating generic rule for recursive elements");
284                         int index = -1;
285                         if (childDescriptor.isWrapCollectionsInElement()) {
286                             index = prefix.indexOf(qualifiedName);
287                             if (index == -1) {
288                                 // shouldn't happen.. 
289                                 log.debug( "Oops - this shouldn't happen" );
290                                 continue;
291                             }
292                             int removeSlash = prefix.endsWith("/")?1:0;
293                             path = "*/" + prefix.substring(index, prefix.length()-removeSlash);
294                             if (log.isTraceEnabled()) {
295                                 log.trace("Added wrapped rule for " + childDescriptor);
296                             }
297                         } else {
298                             // we have a element/element type of thing..
299                             ElementDescriptor[] desc = currentDescriptor.getElementDescriptors();
300                             if (desc.length == 1) {
301                                 path = "*/"+desc[0].getQualifiedName();
302                             }
303                             if (log.isTraceEnabled()) {
304                                 log.trace("Added not wrapped rule for " + childDescriptor);
305                             }
306                         }
307                         addRule( path, childDescriptor, context );
308                         continue;
309                     }
310                     if ( childDescriptor.getUpdater() != null ) {
311                         if (
312                             log.isTraceEnabled() 
313                             && childDescriptor.getUpdater() instanceof MethodUpdater) {
314                             
315                             log.trace("Element has updater "
316                             + ((MethodUpdater) childDescriptor.getUpdater()).getMethod().getName());
317                         }
318                         if ( childDescriptor.isPrimitiveType() ) {
319                             addPrimitiveTypeRule( path, childDescriptor, context );
320                             
321                         } else {
322                             // add the first child to the path
323                             ElementDescriptor[] grandChildren 
324                                 = childDescriptor.getElementDescriptors();
325                             if ( grandChildren != null && grandChildren.length > 0 ) {
326                                 ElementDescriptor grandChild = grandChildren[0];
327                                 String grandChildQName = grandChild.getQualifiedName();
328                                 if ( grandChildQName != null && grandChildQName.length() > 0 ) {
329                                     if (childDescriptor.isWrapCollectionsInElement()) {
330                                         path += '/' + grandChildQName;
331                                         if (log.isTraceEnabled()) {
332                                             log.trace(
333                                     "Descriptor wraps elements in collection, path:" 
334                                                 + path);
335                                         }
336                                         
337                                     } else {
338                                         path = prefix 
339                                             + (prefix.endsWith("/")?"":"/") + grandChildQName;
340                                         if (log.isTraceEnabled()) {
341                                             log.trace(
342                                     "Descriptor does not wrap elements in collection, path:" 
343                                             + path);
344                                         }
345                                     }
346                                 }
347                             }
348                             
349                             // maybe we are adding a primitve type to a collection/array
350                             Class beanClass = childDescriptor.getSingularPropertyType();
351                             if ( XMLIntrospectorHelper.isPrimitiveType( beanClass ) ) {
352                                 addPrimitiveTypeRule( path, childDescriptor, context );
353                                 
354                             } else {
355                                 addRule( path, childDescriptor,  context );
356                             }
357                         }
358                     } else {
359                         if ( log.isTraceEnabled() ) {
360                             log.trace("Element does not have updater: " + childDescriptor);
361                         }
362                         if (childDescriptor.hasAttributes()) {
363                             if ( log.isTraceEnabled() ) {
364                                 log.trace( "Element has attributes, so adding rule anyway : "
365                                             + childDescriptor );
366                             }
367                             addRule(path,childDescriptor, context);
368                         }
369                     }
370     
371                     ElementDescriptor[] grandChildren = childDescriptor.getElementDescriptors();
372                     if ( grandChildren != null && grandChildren.length > 0 ) {
373                         if ( log.isTraceEnabled() ) {
374                             log.trace("Adding grand children @path:" + path);
375                         }
376                         addChildRules( path + '/', childDescriptor, context );
377                     } else if ( log.isTraceEnabled() ) {
378                         log.trace( "No children for " + childDescriptor);
379                     }
380                 }
381             }
382         }
383         
384         /*** Allows the navigation from a reference to a property object to the 
385         * descriptor defining what the property is. i.e. doing the join from a reference 
386         * to a type to lookup its descriptor.
387         * This could be done automatically by the NodeDescriptors. 
388         * Refer to TODO.txt for more info.
389         *
390         * @param propertyDescriptor find descriptor for property object 
391         * referenced by this descriptor
392         * @return descriptor for the singular property class type referenced.
393         */
394         ElementDescriptor getElementDescriptor( ElementDescriptor propertyDescriptor ) {
395             Class beanClass = propertyDescriptor.getSingularPropertyType();
396             if ( beanClass != null && !Map.class.isAssignableFrom( beanClass ) ) {
397                 if (log.isTraceEnabled()) {
398                     log.trace("Filling descriptor for: " + beanClass);
399                 }
400                 try {
401                     XMLBeanInfo xmlInfo = introspector.introspect( beanClass );
402                     if (log.isTraceEnabled()) {
403                         log.trace("Is wrapped? " 
404                         + xmlInfo.getElementDescriptor().isWrapCollectionsInElement());
405                     }
406                     return xmlInfo.getElementDescriptor();
407                     
408                 } catch (Exception e) {
409                     log.warn( "Could not introspect class: " + beanClass, e );
410                 }
411             }
412             // could not find a better descriptor so use the one we've got
413             return propertyDescriptor;
414         }
415         
416         /*** 
417         * Adds a new Digester rule to process the text as a primitive type
418         *
419         * @param path digester path where this rule will be attached
420         * @param childDescriptor update this <code>ElementDescriptor</code> with the body text
421         * @param context the <code>ReadContext</code> against which the elements will be evaluated 
422         */
423         void addPrimitiveTypeRule(
424                                 String path, 
425                                 final ElementDescriptor childDescriptor, 
426                                 final ReadContext context) {
427                                 
428             Rule rule = new Rule() {
429                 public void body(String text) throws Exception {
430                     childDescriptor.getUpdater().update( context, text );
431                 }        
432             };
433             add( path, rule );
434         }
435         
436         /*** 
437         * Adds a new Digester rule to process the text as a primitive type
438         *
439         * @param path digester path where this rule will be attached
440         * @param elementDescriptor update this <code>ElementDescriptor</code> with the body text
441         * @param context the <code>ReadContext</code> against which the elements will be evaluated 
442         */
443         private void addRule( 
444                             String path, 
445                             ElementDescriptor elementDescriptor, 
446                             ReadContext context ) {
447             BeanRule rule = new BeanRule( path + '/', elementDescriptor, context );
448             addRule( path, rule, elementDescriptor, context );
449         }
450         
451         /***
452         * Safely add a rule with given path.
453         *
454         * @param path the digester path to add rule at
455         * @param rule the <code>Rule</code> to add
456         * @param elementDescriptor the <code>ElementDescriptor</code> 
457         * associated with this rule
458         * @param context the <code>ReadContext</code> against which the elements 
459         * will be evaluated        
460         */
461         private void addRule(
462                             String path, 
463                             Rule rule, 
464                             ElementDescriptor elementDescriptor, 
465                             ReadContext context) {
466             if ( add( path, rule ) ) {
467                 // stop infinite recursion by allowing only one rule per path
468                 addChildRules( path + '/', elementDescriptor, context );
469             }
470         }    
471         
472         /***
473          * Add a rule at given path.
474          *
475          * @param path add rule at this path
476          * @param rule the <code>Rule</code> to add
477          *
478          * @return true if this rule was successfully add at given path
479          */
480         private boolean add( String path, Rule rule ) {
481             // only one bean rule allowed per path
482             if ( ! rulesByPath.containsKey( path ) ) {
483                 if ( log.isDebugEnabled() ) {
484                     log.debug( "Added rule for path: " + path + " rule: " + rule );
485                     if (log.isTraceEnabled()) {
486                         log.trace( rulesByPath );
487                     }
488                 }
489                 rulesByPath.put( path, rule );
490                 return true;
491                 
492             } else {
493                 if ( log.isDebugEnabled() ) {
494                     log.debug( "Ignoring duplicate digester rule for path: " 
495                                 + path + " rule: " + rule );
496                     log.debug( "New rule (not added): " + rule );
497                     log.debug( "Existing rule:" + rulesByPath.get(path) );
498                 }
499             }
500             return false;
501         }
502         
503         /***
504         * Rule that creates bean and updates methods.
505         */
506         private class BeanRule extends Rule {
507             
508             /*** The descriptor of this element */
509             private ElementDescriptor descriptor;
510             /*** The Context used when evaluating Updaters */
511             private ReadContext context;
512             /*** In this begin-end loop did we actually create a new bean */
513             private boolean createdBean;
514             /*** The type of the bean to create */
515             private Class beanClass;
516             /*** The prefix added to digester rules */
517             private String pathPrefix;
518             
519             
520             /*** 
521             * Constructor uses standard qualified name from <code>ElementDescriptor</code>.
522             * 
523             * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
524             * @param beanClass the <code>Class</code> to be created
525             * @param context the <code>ReadContext</code> for this rule
526             */
527             public BeanRule( ElementDescriptor descriptor, Class beanClass, ReadContext context ) {
528                 this( descriptor.getQualifiedName() + "/", descriptor, beanClass, context );
529             }
530             
531             /***
532             * Construct a rule based on singular property type of <code>ElementDescriptor</code>.
533             *
534             * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
535             * @param context the <code>Context</code> to be used to evaluate expressions
536             * @param pathPrefix the digester path prefix
537             */
538             public BeanRule(
539                                     String pathPrefix,
540                                     ElementDescriptor descriptor, 
541                                     ReadContext context ) {
542                 this( 
543                         pathPrefix,
544                         descriptor, 
545                         descriptor.getSingularPropertyType(), 
546                         context );
547             }
548             
549             /***
550             * Base constructor (used by other constructors).
551             *
552             * @param descriptor the <code>ElementDescriptor</code> describing the element mapped
553             * @param beanClass the <code>Class</code> of the bean to be created
554             * @param context the <code>Context</code> to be used to evaluate expressions
555             * @param pathPrefix the digester path prefix
556             */
557             private BeanRule(
558                                     String pathPrefix, 
559                                     ElementDescriptor descriptor, 
560                                     Class beanClass,
561                                     ReadContext context ) {
562                 this.descriptor = descriptor;        
563                 this.context = context;
564                 this.beanClass = beanClass;
565                 this.pathPrefix = pathPrefix;
566     
567                 if (log.isTraceEnabled()) {
568                     log.trace("Created bean create rule");
569                     log.trace("Descriptor=" + descriptor);
570                     log.trace("Class=" + beanClass);
571                     log.trace("Path prefix=" + pathPrefix);
572                 }
573             }
574                 
575             // Rule interface
576             //-------------------------------------------------------------------------    
577             
578             /***
579               * @see Rule#begin(String, String, Attributes)
580               */
581             public void begin(String namespace, String name, Attributes attributes) {
582                 if ( log.isDebugEnabled() ) {
583                     log.debug( "Called with descriptor: " + descriptor 
584                                 + " propertyType: " + descriptor.getPropertyType() );
585                 }
586                 
587                 if (log.isTraceEnabled()) {
588                     int attributesLength = attributes.getLength();
589                     if (attributesLength > 0) {
590                         log.trace("Attributes:");
591                     }
592                     for (int i=0, size=attributesLength; i<size; i++) {
593                         log.trace("Local:" + attributes.getLocalName(i));
594                         log.trace("URI:" + attributes.getURI(i));
595                         log.trace("QName:" + attributes.getQName(i));
596                     }
597                 }
598                 
599                 // XXX: if a single rule instance gets reused and nesting occurs
600                 // XXX: we should probably use a stack of booleans to test if we created a bean
601                 // XXX: or let digester take nulls, which would be easier for us ;-)
602                 createdBean = false;
603                 Object instance = null;
604                 // if we are a reference to a type we should lookup the original
605                 // as this ElementDescriptor will be 'hollow' 
606                 // and have no child attributes/elements.
607                 // XXX: this should probably be done by the NodeDescriptors...
608                 ElementDescriptor typeDescriptor = getElementDescriptor( descriptor );
609                 if ( typeDescriptor.getUpdater() == null && beanClass == null ) {
610                     // we try to get the instance from the context.
611                     instance = context.getBean();
612                     if ( instance == null ) {
613                         return;
614                     }
615                 } else {
616                     instance = createBean( namespace, name, attributes );
617                     if ( instance != null ) {
618                         createdBean = true;
619                         context.setBean( instance );
620                         digester.push( instance );
621                     } else {
622                         // we don't do anything if the instance is null.
623                         return;
624                     }
625                 }
626         
627                 // iterate through all attributes        
628                 AttributeDescriptor[] attributeDescriptors 
629                     = typeDescriptor.getAttributeDescriptors();
630                 if ( attributeDescriptors != null ) {
631                     for ( int i = 0, size = attributeDescriptors.length; i < size; i++ ) {
632                         AttributeDescriptor attributeDescriptor = attributeDescriptors[i];
633                         
634                         // The following isn't really the right way to find the attribute
635                         // but it's quite robust.
636                         // The idea is that you try both namespace and local name first
637                         // and if this returns null try the qName.
638                         String value = attributes.getValue( 
639                             attributeDescriptor.getURI(),
640                             attributeDescriptor.getLocalName() 
641                         );
642                         
643                         if ( value == null ) {
644                             value = attributes.getValue(
645                                 attributeDescriptor.getQualifiedName());
646                         }
647                         
648                         if ( log.isTraceEnabled() ) {
649                             log.trace("Attr URL:" + attributeDescriptor.getURI());
650                             log.trace(
651                                         "Attr LocalName:" 
652                                         + attributeDescriptor.getLocalName() );
653                             log.trace(value);
654                         }
655                         
656                         Updater updater = attributeDescriptor.getUpdater();
657                         if ( log.isTraceEnabled() ) {
658                             log.trace("Updater : "+updater);
659                         }
660                         if ( updater != null && value != null ) {
661                             updater.update( context, value );
662                         }
663                     }
664                 }
665                 
666                 if ( log.isTraceEnabled() ) {
667                     log.trace("Created bean " + instance);
668                     log.trace("Path prefix: " + pathPrefix);
669                 }
670                 
671                 // add bean for ID matching
672                 if ( context.getMapIDs() ) {
673                     // XXX need to support custom ID attribute names
674                     // XXX i have a feeling that the current mechanism might need to change
675                     // XXX so i'm leaving this till later
676                     String id = attributes.getValue( "id" );
677                     if ( id != null ) {
678                         context.putBean( id, instance );
679                     }
680                 }
681             }
682             
683             /***
684               * @see Rule#body(String, String, String)
685               */
686             public void body(String namespace, String name, String text) {
687                 
688                 if ( log.isTraceEnabled() ) {
689                     log.trace("Body with text " + text);
690                 }
691                 if ( digester.getCount() > 0 ) {
692                     Context bodyContext = context.newContext( digester.peek() );
693                     // Take the first content descriptor
694                     ElementDescriptor typeDescriptor = getElementDescriptor( descriptor );
695                     TextDescriptor descriptor = typeDescriptor.getPrimaryBodyTextDescriptor();
696                     if ( descriptor != null ) {
697                         if ( log.isTraceEnabled() ) {
698                             log.trace("Setting mixed content for:");
699                             log.trace(descriptor);
700                         }
701                         Updater updater = descriptor.getUpdater();
702                         if ( log.isTraceEnabled() ) {
703                             log.trace( "Updating mixed content with:" );
704                             log.trace( updater );
705                         }
706                         if ( updater != null && text != null ) {
707                             updater.update( bodyContext, text );
708                         }
709                     }
710                 }
711             }
712 
713             /***
714             * Process the end of this element.
715             */
716             public void end() {
717                 if ( createdBean ) {
718                     
719                     // force any setters of the parent bean to be called for this new bean instance
720                     Updater updater = descriptor.getUpdater();
721                     Object instance = context.getBean();
722         
723                     Object top = digester.pop();
724                     if (log.isTraceEnabled()) {
725                         log.trace("Popped " + top);
726                     }
727                     if (digester.getCount() == 0) {
728                         context.setBean(null);
729                     }else{
730                         context.setBean( digester.peek() );
731                     }
732         
733                     if ( updater != null ) {
734                         if ( log.isDebugEnabled() ) {
735                             log.debug( "Calling updater for: " + descriptor + " with: " 
736                                 + instance + " on bean: " + context.getBean() );
737                         }
738                         updater.update( context, instance );
739                     } else {
740                         if ( log.isDebugEnabled() ) {
741                             log.debug( "No updater for: " + descriptor + " with: " 
742                                 + instance + " on bean: " + context.getBean() );
743                         }
744                     }
745                 }
746             }
747         
748             /*** 
749              * Tidy up.
750              */
751             public void finish() {
752                 //
753                 // Clear indexed beans so that we're ready to process next document
754                 //
755                 baseContext.clearBeans();
756             }
757         
758         
759             // Implementation methods
760             //-------------------------------------------------------------------------    
761             
762             /*** 
763             * Factory method to create new bean instances 
764             *
765             * @param namespace the namespace for the element
766             * @param name the local name
767             * @param attributes the <code>Attributes</code> used to match <code>ID/IDREF</code>
768             * @return the created bean
769             */
770             protected Object createBean( String namespace, String name, Attributes attributes ) {
771                 // todo: recycle element mappings 
772                 ElementMapping mapping = new ElementMapping();
773                 mapping.setType( beanClass );
774                 mapping.setNamespace( namespace );
775                 mapping.setName( name );
776                 mapping.setAttributes( attributes );
777                 mapping.setDescriptor( descriptor );
778                 
779                 Object newInstance = context.getBeanCreationChain().create( mapping, context );
780                 
781                 return newInstance;
782             }    
783             
784             /***
785             * Return something meaningful for logging.
786             *
787             * @return something useful for logging
788             */
789             public String toString() {
790                 return "BeanRule [path prefix=" + pathPrefix + " descriptor=" + descriptor + "]";
791             }
792         }
793     }
794 }  
795