View Javadoc

1   package org.apache.commons.betwixt.digester;
2   /*
3    * Copyright 2001-2004 The Apache Software Foundation.
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * 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  import java.beans.PropertyDescriptor;
18  import java.lang.reflect.Method;
19  import java.util.Map;
20  
21  import org.apache.commons.betwixt.ElementDescriptor;
22  import org.apache.commons.betwixt.XMLBeanInfo;
23  import org.apache.commons.betwixt.XMLUtils;
24  import org.apache.commons.betwixt.expression.ConstantExpression;
25  import org.apache.commons.betwixt.expression.IteratorExpression;
26  import org.apache.commons.betwixt.expression.MethodExpression;
27  import org.apache.commons.betwixt.expression.MethodUpdater;
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.xml.sax.Attributes;
31  import org.xml.sax.SAXException;
32  
33  /*** 
34    * <p><code>ElementRule</code> the digester Rule for parsing 
35    * the &lt;element&gt; elements.</p>
36    *
37    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
38    */
39  public class ElementRule extends MappedPropertyRule {
40  
41      /*** Logger */
42      private static Log log = LogFactory.getLog( ElementRule.class );
43      /*** 
44       * Sets the log for this class 
45       * 
46       * @param newLog the new Log implementation for this class to use
47       * @since 0.5
48       */
49      public static final void setLog(Log newLog) {
50          log = newLog;
51      }
52  
53      /*** Class for which the .bewixt file is being digested */
54      private Class beanClass;
55      /*** Base constructor */
56      public ElementRule() {}
57      
58      // Rule interface
59      //-------------------------------------------------------------------------    
60      
61      /***
62       * Process the beginning of this element.
63       *
64       * @param attributes The attribute list of this element
65       * @throws SAXException 1. If this tag's parent is not either an info or element tag.
66       * 2. If the name attribute is not valid XML element name.
67       * 3. If the name attribute is not present 
68       * 4. If the class attribute is not a loadable (fully qualified) class name
69       */
70      public void begin(String name, String namespace, Attributes attributes) throws SAXException {
71          String nameAttributeValue = attributes.getValue( "name" );
72          
73          ElementDescriptor descriptor = new ElementDescriptor();
74          descriptor.setLocalName( nameAttributeValue );
75          String uri = attributes.getValue( "uri" );
76          String qName = nameAttributeValue;
77          if ( uri != null && nameAttributeValue != null) {
78              descriptor.setURI( uri );  
79              String prefix = getXMLIntrospector().getConfiguration().getPrefixMapper().getPrefix(uri);
80              qName = prefix + ":" + nameAttributeValue;
81          }
82          descriptor.setQualifiedName( qName );
83          
84          String propertyName = attributes.getValue( "property" );
85          descriptor.setPropertyName( propertyName );
86          
87          String propertyType = attributes.getValue( "type" );
88          
89          if (log.isTraceEnabled()) {
90              log.trace(
91                      "(BEGIN) name=" + nameAttributeValue + " uri=" + uri 
92                      + " property=" + propertyName + " type=" + propertyType);
93          }
94          
95          // set mapping derivation
96          String mappingDerivation = attributes.getValue( "mappingDerivation" );
97          if ( "introspection".equals(mappingDerivation) ) {
98              descriptor.setUseBindTimeTypeForMapping( false );
99          } else if ( "bind".equals(mappingDerivation) ) {
100             descriptor.setUseBindTimeTypeForMapping( true );
101         }
102         
103         // set the property type using reflection
104         descriptor.setPropertyType( 
105             getPropertyType( propertyType, beanClass, propertyName ) 
106         );
107         
108         boolean isCollective = getXMLIntrospector().getConfiguration()
109         .isLoopType(descriptor.getPropertyType());
110         
111         descriptor.setCollective(isCollective);
112         
113         // check that the name attribute is present 
114         if ( !isCollective && (nameAttributeValue == null || nameAttributeValue.trim().equals( "" ) )) {
115             // allow polymorphic mappings but log note for user
116             log.info("No name attribute has been specified. This element will be polymorphic.");
117         }
118         
119         // check that name is well formed 
120         if ( nameAttributeValue != null && !XMLUtils.isWellFormedXMLName( nameAttributeValue ) ) {
121             throw new SAXException("'" + nameAttributeValue + "' would not be a well formed xml element name.");
122         }
123         
124         
125         String implementationClass = attributes.getValue( "class" );
126         if ( log.isTraceEnabled() ) {
127             log.trace("'class' attribute=" + implementationClass);
128         }
129         if ( implementationClass != null ) {
130             try {
131                 
132                 Class clazz = Class.forName(implementationClass);
133                 descriptor.setImplementationClass( clazz );
134                 
135             } catch (Exception e)  {
136                 if ( log.isDebugEnabled() ) {
137                     log.debug("Cannot load class named: " + implementationClass, e);
138                 }
139                 throw new SAXException("Cannot load class named: " + implementationClass);
140             }
141         }
142         
143         if ( propertyName != null && propertyName.length() > 0 ) {
144             configureDescriptor(descriptor, attributes.getValue( "updater" ));
145             
146         } else {
147             String value = attributes.getValue( "value" );
148             if ( value != null ) {
149                 descriptor.setTextExpression( new ConstantExpression( value ) );
150             }
151         }
152         
153         Object top = digester.peek();
154         if ( top instanceof XMLBeanInfo ) {
155             XMLBeanInfo beanInfo = (XMLBeanInfo) top;
156             beanInfo.setElementDescriptor( descriptor );
157             beanClass = beanInfo.getBeanClass();
158             descriptor.setPropertyType( beanClass );
159             
160         } else if ( top instanceof ElementDescriptor ) {
161             ElementDescriptor parent = (ElementDescriptor) top;
162             parent.addElementDescriptor( descriptor );
163             
164         } else {
165             throw new SAXException( "Invalid use of <element>. It should " 
166                 + "be nested inside <info> or other <element> nodes" );
167         }
168 
169         digester.push(descriptor);        
170     }
171 
172 
173     /***
174      * Process the end of this element.
175      */
176     public void end(String name, String namespace) {
177         Object top = digester.pop();
178     }
179 
180     
181     // Implementation methods
182     //-------------------------------------------------------------------------    
183     
184     /*** 
185      * Sets the Expression and Updater from a bean property name 
186      * Uses the default updater (from the standard java bean property).
187      *
188      * @param elementDescriptor configure this <code>ElementDescriptor</code>
189      * @since 0.5
190      */
191     protected void configureDescriptor(ElementDescriptor elementDescriptor) {
192         configureDescriptor( elementDescriptor, null );
193     }       
194     
195     /*** 
196      * Sets the Expression and Updater from a bean property name 
197      * Allows a custom updater to be passed in.
198      *
199      * @param elementDescriptor configure this <code>ElementDescriptor</code>
200      * @param updateMethodName custom update method. If null, then use standard
201      * @since 0.5
202      */
203     protected void configureDescriptor(
204                                         ElementDescriptor elementDescriptor,
205                                         String updateMethodName) {
206         Class beanClass = getBeanClass();
207         if ( beanClass != null ) {
208             String name = elementDescriptor.getPropertyName();
209             PropertyDescriptor descriptor = 
210                 getPropertyDescriptor( beanClass, name );
211             
212             if ( descriptor != null ) { 
213                 configureProperty( 
214                                         elementDescriptor, 
215                                         descriptor, 
216                                         updateMethodName, 
217                                         beanClass );
218                 
219                 getProcessedPropertyNameSet().add( name );
220             }
221         }
222     }  
223     
224                                     
225     /***
226      * Configure an <code>ElementDescriptor</code> from a <code>PropertyDescriptor</code>.
227      * A custom update method may be set.
228      *
229      * @param elementDescriptor configure this <code>ElementDescriptor</code>
230      * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
231      * @param updateMethodName the name of the custom updater method to user. 
232      * If null, then then 
233      * @param beanClass the <code>Class</code> from which the update method should be found.
234      * This may be null only when <code>updateMethodName</code> is also null.
235      */
236     private void configureProperty( 
237                                     ElementDescriptor elementDescriptor, 
238                                     PropertyDescriptor propertyDescriptor,
239                                     String updateMethodName,
240                                     Class beanClass ) {
241         
242         Class type = propertyDescriptor.getPropertyType();
243         Method readMethod = propertyDescriptor.getReadMethod();
244         Method writeMethod = propertyDescriptor.getWriteMethod();
245         
246         elementDescriptor.setPropertyType( type );        
247         
248         // TODO: associate more bean information with the descriptor?
249         //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
250         //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
251         
252         if ( readMethod == null ) {
253             log.trace( "No read method" );
254             return;
255         }
256         
257         if ( log.isTraceEnabled() ) {
258             log.trace( "Read method=" + readMethod.getName() );
259         }
260         
261         // choose response from property type
262 
263         if ( getXMLIntrospector().isPrimitiveType( type ) ) {
264             elementDescriptor.setTextExpression( new MethodExpression( readMethod ) );
265             
266         } else if ( getXMLIntrospector().isLoopType( type ) ) {
267             log.trace("Loop type ??");
268             
269             // don't wrap this in an extra element as its specified in the 
270             // XML descriptor so no need.            
271             elementDescriptor.setContextExpression(
272                 new IteratorExpression( new MethodExpression( readMethod ) )
273             );
274             elementDescriptor.setHollow(true);
275 
276             writeMethod = null;
277             
278             if (Map.class.isAssignableFrom(type)) {
279                 elementDescriptor.setLocalName( "entry" );
280                 // add elements for reading
281                 ElementDescriptor keyDescriptor = new ElementDescriptor( "key" );
282                 keyDescriptor.setHollow( true );
283                 elementDescriptor.addElementDescriptor( keyDescriptor );
284             
285                 ElementDescriptor valueDescriptor = new ElementDescriptor( "value" );
286                 valueDescriptor.setHollow( true );
287                 elementDescriptor.addElementDescriptor( valueDescriptor );
288             }
289             
290         } else {
291             log.trace( "Standard property" );
292             elementDescriptor.setHollow(true);
293             elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
294         }
295     
296         // see if we have a custom method update name
297         if (updateMethodName == null) {
298             // set standard write method
299             if ( writeMethod != null ) {
300                 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
301             }
302             
303         } else {
304             // see if we can find and set the custom method
305             if ( log.isTraceEnabled() ) {
306                 log.trace( "Finding custom method: " );
307                 log.trace( "  on:" + beanClass );
308                 log.trace( "  name:" + updateMethodName );
309             }
310             
311             Method updateMethod = null;
312             Method[] methods = beanClass.getMethods();
313             for ( int i = 0, size = methods.length; i < size; i++ ) {
314                 Method method = methods[i];
315                 if ( updateMethodName.equals( method.getName() ) ) {
316                     // we have a matching name
317                     // check paramters are correct
318                     if (methods[i].getParameterTypes().length == 1) {
319                         // we'll use first match
320                         updateMethod = methods[i];
321                         if ( log.isTraceEnabled() ) {
322                             log.trace("Matched method:" + updateMethod);
323                         } 
324                         // done since we're using the first match
325                         break;
326                     }
327                 }
328             }
329             
330             if (updateMethod == null) {
331                 if ( log.isInfoEnabled() ) {
332                     
333                     log.info("No method with name '" + updateMethodName + "' found for update");
334                 }
335             } else {
336     
337                 elementDescriptor.setUpdater( new MethodUpdater( updateMethod ) );
338                 elementDescriptor.setSingularPropertyType( updateMethod.getParameterTypes()[0] );
339                 if ( log.isTraceEnabled() ) {
340                     log.trace( "Set custom updater on " + elementDescriptor);
341                 }
342             }
343         }
344     }
345 }