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