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          // check that the name attribute is present 
74          if ( nameAttributeValue == null || nameAttributeValue.trim().equals( "" ) ) {
75              throw new SAXException("Name attribute is required.");
76          }
77          
78          // check that name is well formed 
79          if ( !XMLUtils.isWellFormedXMLName( nameAttributeValue ) ) {
80              throw new SAXException("'" + nameAttributeValue + "' would not be a well formed xml element name.");
81          }
82          
83          ElementDescriptor descriptor = new ElementDescriptor();
84          descriptor.setLocalName( nameAttributeValue );
85          String uri = attributes.getValue( "uri" );
86          String qName = nameAttributeValue;
87          if ( uri != null ) {
88              descriptor.setURI( uri );  
89              String prefix = getXMLIntrospector().getConfiguration().getPrefixMapper().getPrefix(uri);
90              qName = prefix + ":" + nameAttributeValue;
91          }
92          descriptor.setQualifiedName( qName );
93          
94          String propertyName = attributes.getValue( "property" );
95          descriptor.setPropertyName( propertyName );
96          
97          String propertyType = attributes.getValue( "type" );
98          
99          if (log.isTraceEnabled()) {
100             log.trace(
101                     "(BEGIN) name=" + nameAttributeValue + " uri=" + uri 
102                     + " property=" + propertyName + " type=" + propertyType);
103         }
104         
105         // set the property type using reflection
106         descriptor.setPropertyType( 
107             getPropertyType( propertyType, beanClass, propertyName ) 
108         );
109         
110         String implementationClass = attributes.getValue( "class" );
111         if ( log.isTraceEnabled() ) {
112             log.trace("'class' attribute=" + implementationClass);
113         }
114         if ( implementationClass != null ) {
115             try {
116                 
117                 Class clazz = Class.forName(implementationClass);
118                 descriptor.setImplementationClass( clazz );
119                 
120             } catch (Exception e)  {
121                 if ( log.isDebugEnabled() ) {
122                     log.debug("Cannot load class named: " + implementationClass, e);
123                 }
124                 throw new SAXException("Cannot load class named: " + implementationClass);
125             }
126         }
127         
128         if ( propertyName != null && propertyName.length() > 0 ) {
129             configureDescriptor(descriptor, attributes.getValue( "updater" ));
130             
131         } else {
132             String value = attributes.getValue( "value" );
133             if ( value != null ) {
134                 descriptor.setTextExpression( new ConstantExpression( value ) );
135             }
136         }
137         
138         Object top = digester.peek();
139         if ( top instanceof XMLBeanInfo ) {
140             XMLBeanInfo beanInfo = (XMLBeanInfo) top;
141             beanInfo.setElementDescriptor( descriptor );
142             beanClass = beanInfo.getBeanClass();
143             descriptor.setPropertyType( beanClass );
144             
145         } else if ( top instanceof ElementDescriptor ) {
146             ElementDescriptor parent = (ElementDescriptor) top;
147             parent.addElementDescriptor( descriptor );
148             
149         } else {
150             throw new SAXException( "Invalid use of <element>. It should " 
151                 + "be nested inside <info> or other <element> nodes" );
152         }
153 
154         digester.push(descriptor);        
155     }
156 
157 
158     /***
159      * Process the end of this element.
160      */
161     public void end(String name, String namespace) {
162         Object top = digester.pop();
163     }
164 
165     
166     // Implementation methods
167     //-------------------------------------------------------------------------    
168     
169     /*** 
170      * Sets the Expression and Updater from a bean property name 
171      * Uses the default updater (from the standard java bean property).
172      *
173      * @param elementDescriptor configure this <code>ElementDescriptor</code>
174      * @since 0.5
175      */
176     protected void configureDescriptor(ElementDescriptor elementDescriptor) {
177         configureDescriptor( elementDescriptor, null );
178     }       
179     
180     /*** 
181      * Sets the Expression and Updater from a bean property name 
182      * Allows a custom updater to be passed in.
183      *
184      * @param elementDescriptor configure this <code>ElementDescriptor</code>
185      * @param updateMethodName custom update method. If null, then use standard
186      * @since 0.5
187      */
188     protected void configureDescriptor(
189                                         ElementDescriptor elementDescriptor,
190                                         String updateMethodName) {
191         Class beanClass = getBeanClass();
192         if ( beanClass != null ) {
193             String name = elementDescriptor.getPropertyName();
194             PropertyDescriptor descriptor = 
195                 getPropertyDescriptor( beanClass, name );
196             
197             if ( descriptor != null ) { 
198                 configureProperty( 
199                                         elementDescriptor, 
200                                         descriptor, 
201                                         updateMethodName, 
202                                         beanClass );
203                 
204                 getProcessedPropertyNameSet().add( name );
205             }
206         }
207     }  
208     
209                                     
210     /***
211      * Configure an <code>ElementDescriptor</code> from a <code>PropertyDescriptor</code>.
212      * A custom update method may be set.
213      *
214      * @param elementDescriptor configure this <code>ElementDescriptor</code>
215      * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
216      * @param updateMethodName the name of the custom updater method to user. 
217      * If null, then then 
218      * @param beanClass the <code>Class</code> from which the update method should be found.
219      * This may be null only when <code>updateMethodName</code> is also null.
220      */
221     private void configureProperty( 
222                                     ElementDescriptor elementDescriptor, 
223                                     PropertyDescriptor propertyDescriptor,
224                                     String updateMethodName,
225                                     Class beanClass ) {
226         
227         Class type = propertyDescriptor.getPropertyType();
228         Method readMethod = propertyDescriptor.getReadMethod();
229         Method writeMethod = propertyDescriptor.getWriteMethod();
230         
231         String existingLocalName = elementDescriptor.getLocalName();
232         if (existingLocalName == null || "".equals(existingLocalName)) {
233             elementDescriptor.setLocalName( propertyDescriptor.getName() );
234         }
235         elementDescriptor.setPropertyType( type );        
236         
237         // TODO: associate more bean information with the descriptor?
238         //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
239         //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
240         
241         if ( readMethod == null ) {
242             log.trace( "No read method" );
243             return;
244         }
245         
246         if ( log.isTraceEnabled() ) {
247             log.trace( "Read method=" + readMethod.getName() );
248         }
249         
250         // choose response from property type
251         
252         // TODO: ignore class property ??
253         if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) {
254             log.trace( "Ignoring class property" );
255             return;
256         }
257         if ( getXMLIntrospector().isPrimitiveType( type ) ) {
258             elementDescriptor.setTextExpression( new MethodExpression( readMethod ) );
259             
260         } else if ( getXMLIntrospector().isLoopType( type ) ) {
261             log.trace("Loop type ??");
262             
263             // don't wrap this in an extra element as its specified in the 
264             // XML descriptor so no need.            
265             elementDescriptor.setContextExpression(
266                 new IteratorExpression( new MethodExpression( readMethod ) )
267             );
268             elementDescriptor.setHollow(true);
269 
270             writeMethod = null;
271             
272             if (Map.class.isAssignableFrom(type)) {
273                 elementDescriptor.setLocalName( "entry" );
274                 // add elements for reading
275                 ElementDescriptor keyDescriptor = new ElementDescriptor( "key" );
276                 keyDescriptor.setHollow( true );
277                 elementDescriptor.addElementDescriptor( keyDescriptor );
278             
279                 ElementDescriptor valueDescriptor = new ElementDescriptor( "value" );
280                 valueDescriptor.setHollow( true );
281                 elementDescriptor.addElementDescriptor( valueDescriptor );
282             }
283             
284         } else {
285             log.trace( "Standard property" );
286             elementDescriptor.setHollow(true);
287             elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
288         }
289     
290         // see if we have a custom method update name
291         if (updateMethodName == null) {
292             // set standard write method
293             if ( writeMethod != null ) {
294                 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
295             }
296             
297         } else {
298             // see if we can find and set the custom method
299             if ( log.isTraceEnabled() ) {
300                 log.trace( "Finding custom method: " );
301                 log.trace( "  on:" + beanClass );
302                 log.trace( "  name:" + updateMethodName );
303             }
304             
305             Method updateMethod = null;
306             Method[] methods = beanClass.getMethods();
307             for ( int i = 0, size = methods.length; i < size; i++ ) {
308                 Method method = methods[i];
309                 if ( updateMethodName.equals( method.getName() ) ) {
310                     // we have a matching name
311                     // check paramters are correct
312                     if (methods[i].getParameterTypes().length == 1) {
313                         // we'll use first match
314                         updateMethod = methods[i];
315                         if ( log.isTraceEnabled() ) {
316                             log.trace("Matched method:" + updateMethod);
317                         } 
318                         // done since we're using the first match
319                         break;
320                     }
321                 }
322             }
323             
324             if (updateMethod == null) {
325                 if ( log.isInfoEnabled() ) {
326                     
327                     log.info("No method with name '" + updateMethodName + "' found for update");
328                 }
329             } else {
330     
331                 elementDescriptor.setUpdater( new MethodUpdater( updateMethod ) );
332                 elementDescriptor.setSingularPropertyType( updateMethod.getParameterTypes()[0] );
333                 if ( log.isTraceEnabled() ) {
334                     log.trace( "Set custom updater on " + elementDescriptor);
335                 }
336             }
337         }
338     }
339 }