1 package org.apache.commons.betwixt.digester;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 <element> 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
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
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
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
114 if ( !isCollective && (nameAttributeValue == null || nameAttributeValue.trim().equals( "" ) )) {
115
116 log.info("No name attribute has been specified. This element will be polymorphic.");
117 }
118
119
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
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
249
250
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
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
270
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
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
297 if (updateMethodName == null) {
298
299 if ( writeMethod != null ) {
300 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
301 }
302
303 } else {
304
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
317
318 if (methods[i].getParameterTypes().length == 1) {
319
320 updateMethod = methods[i];
321 if ( log.isTraceEnabled() ) {
322 log.trace("Matched method:" + updateMethod);
323 }
324
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 }