1. /*
  2. * Copyright 2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.betwixt.io.read;
  17. import java.util.Map;
  18. import org.apache.commons.betwixt.AttributeDescriptor;
  19. import org.apache.commons.betwixt.ElementDescriptor;
  20. import org.apache.commons.betwixt.TextDescriptor;
  21. import org.apache.commons.betwixt.XMLBeanInfo;
  22. import org.apache.commons.betwixt.expression.Updater;
  23. import org.apache.commons.logging.Log;
  24. import org.xml.sax.Attributes;
  25. /**
  26. * Action that creates and binds a new bean instance.
  27. *
  28. * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
  29. * @version $Revision: 1.2 $
  30. */
  31. public class BeanBindAction extends MappingAction.Base {
  32. /** Singleton instance */
  33. public static final BeanBindAction INSTANCE = new BeanBindAction();
  34. /**
  35. * Begins a new element which is to be bound to a bean.
  36. */
  37. public MappingAction begin(
  38. String namespace,
  39. String name,
  40. Attributes attributes,
  41. ReadContext context)
  42. throws Exception {
  43. Log log = context.getLog();
  44. ElementDescriptor computedDescriptor = context.getCurrentDescriptor();
  45. if (log.isTraceEnabled()) {
  46. log.trace("Element Pushed: " + name);
  47. }
  48. // default to ignoring the current element
  49. MappingAction action = MappingAction.EMPTY;
  50. Object instance = null;
  51. Class beanClass = null;
  52. if (computedDescriptor == null) {
  53. log.trace("No Descriptor");
  54. } else {
  55. beanClass = computedDescriptor.getSingularPropertyType();
  56. }
  57. // TODO: this is a bit of a workaround
  58. // need to come up with a better way of doing maps
  59. if (beanClass != null && !Map.class.isAssignableFrom(beanClass)) {
  60. instance =
  61. createBean(
  62. namespace,
  63. name,
  64. attributes,
  65. computedDescriptor,
  66. context);
  67. if (instance != null) {
  68. action = this;
  69. context.markClassMap(beanClass);
  70. if (log.isTraceEnabled()) {
  71. log.trace("Marked: " + beanClass);
  72. }
  73. context.pushBean(instance);
  74. // if we are a reference to a type we should lookup the original
  75. // as this ElementDescriptor will be 'hollow'
  76. // and have no child attributes/elements.
  77. // XXX: this should probably be done by the NodeDescriptors...
  78. ElementDescriptor typeDescriptor =
  79. getElementDescriptor(computedDescriptor, context);
  80. //ElementDescriptor typeDescriptor = descriptor;
  81. // iterate through all attributes
  82. AttributeDescriptor[] attributeDescriptors =
  83. typeDescriptor.getAttributeDescriptors();
  84. context.populateAttributes(attributeDescriptors, attributes);
  85. if (log.isTraceEnabled()) {
  86. log.trace("Created bean " + instance);
  87. }
  88. // add bean for ID matching
  89. if (context.getMapIDs()) {
  90. // XXX need to support custom ID attribute names
  91. // XXX i have a feeling that the current mechanism might need to change
  92. // XXX so i'm leaving this till later
  93. String id = attributes.getValue("id");
  94. if (id != null) {
  95. context.putBean(id, instance);
  96. }
  97. }
  98. }
  99. }
  100. return action;
  101. }
  102. public void body(String text, ReadContext context) throws Exception {
  103. Log log = context.getLog();
  104. // Take the first content descriptor
  105. ElementDescriptor currentDescriptor = context.getCurrentDescriptor();
  106. if (currentDescriptor == null) {
  107. if (log.isTraceEnabled()) {
  108. log.trace("path descriptor is null:");
  109. }
  110. } else {
  111. TextDescriptor bodyTextdescriptor =
  112. currentDescriptor.getPrimaryBodyTextDescriptor();
  113. if (bodyTextdescriptor != null) {
  114. if (log.isTraceEnabled()) {
  115. log.trace("Setting mixed content for:");
  116. log.trace(bodyTextdescriptor);
  117. }
  118. Updater updater = bodyTextdescriptor.getUpdater();
  119. if (log.isTraceEnabled())
  120. {
  121. log.trace("Updating mixed content with:");
  122. log.trace(updater);
  123. }
  124. if (updater != null && text != null) {
  125. updater.update(context, text);
  126. }
  127. }
  128. }
  129. }
  130. public void end(ReadContext context) throws Exception {
  131. // force any setters of the parent bean to be called for this new bean instance
  132. Object instance = context.popBean();
  133. update(context, instance);
  134. }
  135. private void update(ReadContext context, Object value) throws Exception {
  136. Log log = context.getLog();
  137. Updater updater = context.getCurrentUpdater();
  138. if ( updater == null ) {
  139. if ( context.getLog().isTraceEnabled() ) {
  140. context.getLog().trace("No updater for " + context.getCurrentElement());
  141. }
  142. } else {
  143. updater.update(context, value);
  144. }
  145. String poppedElement = context.popElement();
  146. }
  147. /**
  148. * Factory method to create new bean instances
  149. *
  150. * @param namespace the namespace for the element
  151. * @param name the local name
  152. * @param attributes the <code>Attributes</code> used to match <code>ID/IDREF</code>
  153. * @return the created bean
  154. */
  155. protected Object createBean(
  156. String namespace,
  157. String name,
  158. Attributes attributes,
  159. ElementDescriptor descriptor,
  160. ReadContext context) {
  161. // TODO: recycle element mappings
  162. // Maybe should move the current mapping into the context
  163. ElementMapping mapping = new ElementMapping();
  164. Class beanClass = descriptor.getSingularPropertyType();
  165. if (beanClass != null && beanClass.isArray()) {
  166. beanClass = beanClass.getComponentType();
  167. }
  168. // TODO: beanClass can be deduced from descriptor
  169. // so probably
  170. mapping.setType(beanClass);
  171. mapping.setNamespace(namespace);
  172. mapping.setName(name);
  173. mapping.setAttributes(attributes);
  174. mapping.setDescriptor(descriptor);
  175. Object newInstance =
  176. context.getBeanCreationChain().create(mapping, context);
  177. return newInstance;
  178. }
  179. /** Allows the navigation from a reference to a property object to the
  180. * descriptor defining what the property is. i.e. doing the join from a reference
  181. * to a type to lookup its descriptor.
  182. * This could be done automatically by the NodeDescriptors.
  183. * Refer to TODO.txt for more info.
  184. *
  185. * @param propertyDescriptor find descriptor for property object
  186. * referenced by this descriptor
  187. * @return descriptor for the singular property class type referenced.
  188. */
  189. ElementDescriptor getElementDescriptor(
  190. ElementDescriptor propertyDescriptor,
  191. ReadContext context) {
  192. Log log = context.getLog();
  193. Class beanClass = propertyDescriptor.getSingularPropertyType();
  194. if (beanClass != null && !Map.class.isAssignableFrom(beanClass)) {
  195. if (beanClass.isArray()) {
  196. beanClass = beanClass.getComponentType();
  197. }
  198. if (log.isTraceEnabled()) {
  199. log.trace("Filling descriptor for: " + beanClass);
  200. }
  201. try {
  202. XMLBeanInfo xmlInfo =
  203. context.getXMLIntrospector().introspect(beanClass);
  204. return xmlInfo.getElementDescriptor();
  205. } catch (Exception e) {
  206. log.warn("Could not introspect class: " + beanClass, e);
  207. }
  208. }
  209. // could not find a better descriptor so use the one we've got
  210. return propertyDescriptor;
  211. }
  212. }