1 package org.apache.commons.betwixt.digester;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import java.beans.PropertyDescriptor;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Modifier;
22 import java.util.Map;
23
24 import org.apache.commons.betwixt.AttributeDescriptor;
25 import org.apache.commons.betwixt.ElementDescriptor;
26 import org.apache.commons.betwixt.XMLBeanInfo;
27 import org.apache.commons.betwixt.XMLUtils;
28 import org.apache.commons.betwixt.expression.ConstantExpression;
29 import org.apache.commons.betwixt.expression.Expression;
30 import org.apache.commons.betwixt.expression.IteratorExpression;
31 import org.apache.commons.betwixt.expression.MethodExpression;
32 import org.apache.commons.betwixt.expression.MethodUpdater;
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.xml.sax.Attributes;
36 import org.xml.sax.SAXException;
37
38 /***
39 * <p>
40 * <code>ElementRule</code> the digester Rule for parsing the <element>
41 * elements.
42 * </p>
43 *
44 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
45 */
46 public class ElementRule extends MappedPropertyRule {
47
48 /*** Logger */
49 private static Log log = LogFactory.getLog(ElementRule.class);
50
51 /***
52 * Sets the log for this class
53 *
54 * @param newLog
55 * the new Log implementation for this class to use
56 * @since 0.5
57 */
58 public static final void setLog(Log newLog) {
59 log = newLog;
60 }
61
62 /*** Class for which the .bewixt file is being digested */
63 private Class beanClass;
64
65 /*** Base constructor */
66 public ElementRule() {
67 }
68
69
70
71
72 /***
73 * Process the beginning of this element.
74 *
75 * @param attributes
76 * The attribute list of this element
77 * @throws SAXException
78 * 1. If this tag's parent is not either an info or element tag.
79 * 2. If the name attribute is not valid XML element name. 3. If
80 * the name attribute is not present 4. If the class attribute
81 * is not a loadable (fully qualified) class name
82 */
83 public void begin(String name, String namespace, Attributes attributes)
84 throws SAXException {
85 String nameAttributeValue = attributes.getValue("name");
86
87 ElementDescriptor descriptor = new ElementDescriptor();
88 descriptor.setLocalName(nameAttributeValue);
89 String uri = attributes.getValue("uri");
90 String qName = nameAttributeValue;
91 if (uri != null && nameAttributeValue != null) {
92 descriptor.setURI(uri);
93 String prefix = getXMLIntrospector().getConfiguration()
94 .getPrefixMapper().getPrefix(uri);
95 qName = prefix + ":" + nameAttributeValue;
96 }
97 descriptor.setQualifiedName(qName);
98
99 String propertyName = attributes.getValue("property");
100 descriptor.setPropertyName(propertyName);
101
102 String propertyType = attributes.getValue("type");
103
104 if (log.isTraceEnabled()) {
105 log.trace("(BEGIN) name=" + nameAttributeValue + " uri=" + uri
106 + " property=" + propertyName + " type=" + propertyType);
107 }
108
109
110 String mappingDerivation = attributes.getValue("mappingDerivation");
111 if ("introspection".equals(mappingDerivation)) {
112 descriptor.setUseBindTimeTypeForMapping(false);
113 } else if ("bind".equals(mappingDerivation)) {
114 descriptor.setUseBindTimeTypeForMapping(true);
115 }
116
117
118 descriptor.setPropertyType(getPropertyType(propertyType, beanClass,
119 propertyName));
120
121 boolean isCollective = getXMLIntrospector().getConfiguration()
122 .isLoopType(descriptor.getPropertyType());
123
124 descriptor.setCollective(isCollective);
125
126
127 if (!isCollective
128 && (nameAttributeValue == null || nameAttributeValue.trim()
129 .equals(""))) {
130
131 log
132 .info("No name attribute has been specified. This element will be polymorphic.");
133 }
134
135
136 if (nameAttributeValue != null
137 && !XMLUtils.isWellFormedXMLName(nameAttributeValue)) {
138 throw new SAXException("'" + nameAttributeValue
139 + "' would not be a well formed xml element name.");
140 }
141
142 String implementationClass = attributes.getValue("class");
143 if (log.isTraceEnabled()) {
144 log.trace("'class' attribute=" + implementationClass);
145 }
146 if (implementationClass != null) {
147 try {
148
149 Class clazz = Class.forName(implementationClass);
150 descriptor.setImplementationClass(clazz);
151
152 } catch (Exception e) {
153 if (log.isDebugEnabled()) {
154 log.debug(
155 "Cannot load class named: " + implementationClass,
156 e);
157 }
158 throw new SAXException("Cannot load class named: "
159 + implementationClass);
160 }
161 }
162
163 if (propertyName != null && propertyName.length() > 0) {
164 boolean forceAccessible = "true".equals(attributes
165 .getValue("forceAccessible"));
166 configureDescriptor(descriptor, attributes.getValue("updater"),
167 forceAccessible);
168
169 } else {
170 String value = attributes.getValue("value");
171 if (value != null) {
172 descriptor.setTextExpression(new ConstantExpression(value));
173 }
174 }
175
176 Object top = digester.peek();
177 if (top instanceof XMLBeanInfo) {
178 XMLBeanInfo beanInfo = (XMLBeanInfo) top;
179 beanInfo.setElementDescriptor(descriptor);
180 beanClass = beanInfo.getBeanClass();
181 descriptor.setPropertyType(beanClass);
182
183 } else if (top instanceof ElementDescriptor) {
184 ElementDescriptor parent = (ElementDescriptor) top;
185 parent.addElementDescriptor(descriptor);
186
187 } else {
188 throw new SAXException("Invalid use of <element>. It should "
189 + "be nested inside <info> or other <element> nodes");
190 }
191
192 digester.push(descriptor);
193 }
194
195 /***
196 * Process the end of this element.
197 */
198 public void end(String name, String namespace) {
199 ElementDescriptor descriptor = (ElementDescriptor)digester.pop();
200
201 final Object peek = digester.peek();
202
203 if(peek instanceof ElementDescriptor) {
204 ElementDescriptor parent = (ElementDescriptor)digester.peek();
205
206
207 if( getXMLIntrospector().getConfiguration().getElementSuppressionStrategy().suppress(descriptor)) {
208 parent.removeElementDescriptor(descriptor);
209 }
210 }
211 }
212
213
214
215
216 /***
217 * Sets the Expression and Updater from a bean property name Uses the
218 * default updater (from the standard java bean property).
219 *
220 * @param elementDescriptor
221 * configure this <code>ElementDescriptor</code>
222 * @since 0.5
223 */
224 protected void configureDescriptor(ElementDescriptor elementDescriptor) {
225 configureDescriptor(elementDescriptor, null);
226 }
227
228 /***
229 * Sets the Expression and Updater from a bean property name Allows a custom
230 * updater to be passed in.
231 *
232 * @param elementDescriptor
233 * configure this <code>ElementDescriptor</code>
234 * @param updateMethodName
235 * custom update method. If null, then use standard
236 * @since 0.5
237 * @deprecated now calls
238 * <code>#configureDescriptor(ElementDescriptor, String, boolean)</code>
239 * which allow accessibility to be forced. The subclassing API
240 * was not really considered carefully when this class was
241 * created. If anyone subclasses this method please contact the
242 * mailing list and suitable hooks will be placed into the code.
243 */
244 protected void configureDescriptor(ElementDescriptor elementDescriptor,
245 String updateMethodName) {
246 configureDescriptor(elementDescriptor, null, false);
247 }
248
249 /***
250 * Sets the Expression and Updater from a bean property name Allows a custom
251 * updater to be passed in.
252 *
253 * @param elementDescriptor
254 * configure this <code>ElementDescriptor</code>
255 * @param updateMethodName
256 * custom update method. If null, then use standard
257 * @param forceAccessible
258 * if true and updateMethodName is not null, then non-public
259 * methods will be searched and made accessible
260 * (Method.setAccessible(true))
261 */
262 private void configureDescriptor(ElementDescriptor elementDescriptor,
263 String updateMethodName, boolean forceAccessible) {
264 Class beanClass = getBeanClass();
265 if (beanClass != null) {
266 String name = elementDescriptor.getPropertyName();
267 PropertyDescriptor descriptor = getPropertyDescriptor(beanClass,
268 name);
269
270 if (descriptor == null) {
271 if (log.isDebugEnabled()) {
272 log.debug("Cannot find property matching " + name);
273 }
274 } else {
275 configureProperty(elementDescriptor, descriptor,
276 updateMethodName, forceAccessible, beanClass);
277
278 getProcessedPropertyNameSet().add(name);
279 }
280 }
281 }
282
283 /***
284 * Configure an <code>ElementDescriptor</code> from a
285 * <code>PropertyDescriptor</code>. A custom update method may be set.
286 *
287 * @param elementDescriptor
288 * configure this <code>ElementDescriptor</code>
289 * @param propertyDescriptor
290 * configure from this <code>PropertyDescriptor</code>
291 * @param updateMethodName
292 * the name of the custom updater method to user. If null, then
293 * then
294 * @param forceAccessible
295 * if true and updateMethodName is not null, then non-public
296 * methods will be searched and made accessible
297 * (Method.setAccessible(true))
298 * @param beanClass
299 * the <code>Class</code> from which the update method should
300 * be found. This may be null only when
301 * <code>updateMethodName</code> is also null.
302 */
303 private void configureProperty(ElementDescriptor elementDescriptor,
304 PropertyDescriptor propertyDescriptor, String updateMethodName,
305 boolean forceAccessible, Class beanClass) {
306
307 Class type = propertyDescriptor.getPropertyType();
308 Method readMethod = propertyDescriptor.getReadMethod();
309 Method writeMethod = propertyDescriptor.getWriteMethod();
310
311 elementDescriptor.setPropertyType(type);
312
313
314
315
316
317
318 if (readMethod == null) {
319 log.trace("No read method");
320 return;
321 }
322
323 if (log.isTraceEnabled()) {
324 log.trace("Read method=" + readMethod.getName());
325 }
326
327
328
329 final MethodExpression methodExpression = new MethodExpression(readMethod);
330 if (getXMLIntrospector().isPrimitiveType(type)) {
331 elementDescriptor
332 .setTextExpression(methodExpression);
333
334 } else if (getXMLIntrospector().isLoopType(type)) {
335 log.trace("Loop type ??");
336
337
338
339 Expression expression = methodExpression;
340
341
342
343 boolean standardProperty = false;
344 if (updateMethodName != null && writeMethod != null && writeMethod.getName().equals(updateMethodName)) {
345 final Class[] parameters = writeMethod.getParameterTypes();
346 if (parameters.length == 1) {
347 Class setterType = parameters[0];
348 if (type.equals(setterType)) {
349 standardProperty = true;
350 }
351 }
352 }
353 if (!standardProperty) {
354 expression = new IteratorExpression(methodExpression);
355 }
356 elementDescriptor.setContextExpression(expression);
357 elementDescriptor.setHollow(true);
358
359 writeMethod = null;
360
361 if (Map.class.isAssignableFrom(type)) {
362 elementDescriptor.setLocalName("entry");
363
364 ElementDescriptor keyDescriptor = new ElementDescriptor("key");
365 keyDescriptor.setHollow(true);
366 elementDescriptor.addElementDescriptor(keyDescriptor);
367
368 ElementDescriptor valueDescriptor = new ElementDescriptor(
369 "value");
370 valueDescriptor.setHollow(true);
371 elementDescriptor.addElementDescriptor(valueDescriptor);
372 }
373
374 } else {
375 log.trace("Standard property");
376 elementDescriptor.setHollow(true);
377 elementDescriptor.setContextExpression(methodExpression);
378 }
379
380
381 if (updateMethodName == null) {
382
383 if (writeMethod != null) {
384 elementDescriptor.setUpdater(new MethodUpdater(writeMethod));
385 }
386
387 } else {
388
389 if (log.isTraceEnabled()) {
390 log.trace("Finding custom method: ");
391 log.trace(" on:" + beanClass);
392 log.trace(" name:" + updateMethodName);
393 }
394
395 Method updateMethod;
396 boolean isMapTypeProperty = Map.class.isAssignableFrom(type);
397 if (forceAccessible) {
398 updateMethod = findAnyMethod(updateMethodName, beanClass, isMapTypeProperty);
399 } else {
400 updateMethod = findPublicMethod(updateMethodName, beanClass, isMapTypeProperty);
401 }
402
403 if (updateMethod == null) {
404 if (log.isInfoEnabled()) {
405
406 log.info("No method with name '" + updateMethodName
407 + "' found for update");
408 }
409 } else {
410
411 if (Map.class.isAssignableFrom(type)) {
412
413 getXMLIntrospector().assignAdder(updateMethod, elementDescriptor);
414
415 } else {
416 elementDescriptor
417 .setUpdater(new MethodUpdater(updateMethod));
418 Class singularType = updateMethod.getParameterTypes()[0];
419 elementDescriptor.setSingularPropertyType(singularType);
420 if (singularType != null)
421 {
422 boolean isPrimitive = getXMLIntrospector().isPrimitiveType(singularType);
423 if (isPrimitive)
424 {
425 log.debug("Primitive collective: setting hollow to false");
426 elementDescriptor.setHollow(false);
427 }
428 }
429 if (log.isTraceEnabled()) {
430 log.trace("Set custom updater on " + elementDescriptor);
431 }
432 }
433 }
434 }
435 }
436
437 private Method findPublicMethod(String updateMethodName, Class beanType, boolean isMapTypeProperty) {
438 Method[] methods = beanType.getMethods();
439 Method updateMethod = searchMethodsForMatch(updateMethodName, methods, isMapTypeProperty);
440 return updateMethod;
441 }
442
443 private Method searchMethodsForMatch(String updateMethodName,
444 Method[] methods, boolean isMapType) {
445 Method updateMethod = null;
446 for (int i = 0, size = methods.length; i < size; i++) {
447 Method method = methods[i];
448 if (updateMethodName.equals(method.getName())) {
449
450
451 int numParams = 1;
452 if (isMapType) {
453
454 numParams = 2;
455 }
456
457
458
459 if (methods[i].getParameterTypes().length == numParams) {
460
461 updateMethod = methods[i];
462 if (log.isTraceEnabled()) {
463 log.trace("Matched method:" + updateMethod);
464 }
465
466 break;
467 }
468 }
469 }
470 return updateMethod;
471 }
472
473 private Method findAnyMethod(String updateMethodName, Class beanType, boolean isMapTypeProperty) {
474
475
476
477 Method updateMethod = null;
478 Class classToTry = beanType;
479 do {
480 Method[] methods = classToTry.getDeclaredMethods();
481 updateMethod = searchMethodsForMatch(updateMethodName, methods, isMapTypeProperty);
482
483
484
485 classToTry = classToTry.getSuperclass();
486 } while (updateMethod == null && classToTry != null);
487
488 if (updateMethod != null) {
489 boolean isPublic = Modifier.isPublic(updateMethod.getModifiers())
490 && Modifier.isPublic(beanType.getModifiers());
491 if (!isPublic) {
492 updateMethod.setAccessible(true);
493 }
494 }
495 return updateMethod;
496 }
497 }