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
74 if ( nameAttributeValue == null || nameAttributeValue.trim().equals( "" ) ) {
75 throw new SAXException("Name attribute is required.");
76 }
77
78
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
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
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
238
239
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
251
252
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
264
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
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
291 if (updateMethodName == null) {
292
293 if ( writeMethod != null ) {
294 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
295 }
296
297 } else {
298
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
311
312 if (methods[i].getParameterTypes().length == 1) {
313
314 updateMethod = methods[i];
315 if ( log.isTraceEnabled() ) {
316 log.trace("Matched method:" + updateMethod);
317 }
318
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 }