1. /*
  2. * Copyright 2001-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;
  17. import org.apache.commons.betwixt.BindingConfiguration;
  18. import org.apache.commons.betwixt.ElementDescriptor;
  19. import org.apache.commons.betwixt.XMLIntrospector;
  20. import org.apache.commons.betwixt.expression.Context;
  21. import org.apache.commons.betwixt.io.read.BeanBindAction;
  22. import org.apache.commons.betwixt.io.read.MappingAction;
  23. import org.apache.commons.betwixt.io.read.ReadConfiguration;
  24. import org.apache.commons.betwixt.io.read.ReadContext;
  25. import org.apache.commons.digester.Digester;
  26. import org.apache.commons.digester.Rule;
  27. import org.apache.commons.digester.RuleSet;
  28. import org.apache.commons.logging.Log;
  29. import org.apache.commons.logging.LogFactory;
  30. import org.xml.sax.Attributes;
  31. /** <p>Sets <code>Betwixt</code> digestion rules for a bean class.</p>
  32. *
  33. * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
  34. * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
  35. * @since 0.5
  36. */
  37. public class BeanRuleSet implements RuleSet {
  38. /** Logger */
  39. private static Log log = LogFactory.getLog(BeanRuleSet.class);
  40. /**
  41. * Set log to be used by <code>BeanRuleSet</code> instances
  42. * @param aLog the <code>Log</code> implementation for this class to log to
  43. */
  44. public static void setLog(Log aLog) {
  45. log = aLog;
  46. }
  47. /** The base path under which the rules will be attached */
  48. private String basePath;
  49. /** The element descriptor for the base */
  50. private ElementDescriptor baseElementDescriptor;
  51. /** The (empty) base context from which all Contexts
  52. with beans are (directly or indirectly) obtained */
  53. private DigesterReadContext context;
  54. /** allows an attribute to be specified to overload the types of beans used */
  55. private String classNameAttribute = "className";
  56. /**
  57. * Base constructor.
  58. *
  59. * @param introspector the <code>XMLIntrospector</code> used to introspect
  60. * @param basePath specifies the (Digester-style) path under which the rules will be attached
  61. * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
  62. * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
  63. * @param matchIDs should ID/IDREFs be used to match beans?
  64. * @deprecated 0.5 use constructor which takes a ReadContext instead
  65. */
  66. public BeanRuleSet(
  67. XMLIntrospector introspector,
  68. String basePath,
  69. ElementDescriptor baseElementDescriptor,
  70. Class baseBeanClass,
  71. boolean matchIDs) {
  72. this.basePath = basePath;
  73. this.baseElementDescriptor = baseElementDescriptor;
  74. BindingConfiguration bindingConfiguration = new BindingConfiguration();
  75. bindingConfiguration.setMapIDs(matchIDs);
  76. context =
  77. new DigesterReadContext(
  78. log,
  79. bindingConfiguration,
  80. new ReadConfiguration());
  81. context.setRootClass(baseBeanClass);
  82. context.setXMLIntrospector(introspector);
  83. }
  84. /**
  85. * Base constructor.
  86. *
  87. * @param introspector the <code>XMLIntrospector</code> used to introspect
  88. * @param basePath specifies the (Digester-style) path under which the rules will be attached
  89. * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
  90. * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
  91. * @param context the root Context that bean carrying Contexts should be obtained from,
  92. * not null
  93. * @deprecated 0.6 use the constructor which takes a ReadContext instead
  94. */
  95. public BeanRuleSet(
  96. XMLIntrospector introspector,
  97. String basePath,
  98. ElementDescriptor baseElementDescriptor,
  99. Context context) {
  100. this.basePath = basePath;
  101. this.baseElementDescriptor = baseElementDescriptor;
  102. this.context =
  103. new DigesterReadContext(context, new ReadConfiguration());
  104. this.context.setRootClass(
  105. baseElementDescriptor.getSingularPropertyType());
  106. this.context.setXMLIntrospector(introspector);
  107. }
  108. /**
  109. * Base constructor.
  110. *
  111. * @param introspector the <code>XMLIntrospector</code> used to introspect
  112. * @param basePath specifies the (Digester-style) path under which the rules will be attached
  113. * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
  114. * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
  115. * @param context the root Context that bean carrying Contexts should be obtained from,
  116. * not null
  117. * @deprecated 0.5 use the constructor which takes a ReadContext instead
  118. */
  119. public BeanRuleSet(
  120. XMLIntrospector introspector,
  121. String basePath,
  122. ElementDescriptor baseElementDescriptor,
  123. Class baseBeanClass,
  124. Context context) {
  125. this(
  126. introspector,
  127. basePath,
  128. baseElementDescriptor,
  129. baseBeanClass,
  130. new ReadContext( context, new ReadConfiguration() ));
  131. }
  132. /**
  133. * Base constructor.
  134. *
  135. * @param introspector the <code>XMLIntrospector</code> used to introspect
  136. * @param basePath specifies the (Digester-style) path under which the rules will be attached
  137. * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules
  138. * @param baseBeanClass the <code>Class</code> whose mapping rules will be created
  139. * @param baseContext the root Context that bean carrying Contexts should be obtained from,
  140. * not null
  141. */
  142. public BeanRuleSet(
  143. XMLIntrospector introspector,
  144. String basePath,
  145. ElementDescriptor baseElementDescriptor,
  146. Class baseBeanClass,
  147. ReadContext baseContext) {
  148. this.basePath = basePath;
  149. this.baseElementDescriptor = baseElementDescriptor;
  150. this.context = new DigesterReadContext(baseContext);
  151. this.context.setRootClass(baseBeanClass);
  152. this.context.setXMLIntrospector(introspector);
  153. }
  154. /**
  155. * The name of the attribute which can be specified in the XML to override the
  156. * type of a bean used at a certain point in the schema.
  157. *
  158. * <p>The default value is 'className'.</p>
  159. *
  160. * @return The name of the attribute used to overload the class name of a bean
  161. */
  162. public String getClassNameAttribute() {
  163. return context.getClassNameAttribute();
  164. }
  165. /**
  166. * Sets the name of the attribute which can be specified in
  167. * the XML to override the type of a bean used at a certain
  168. * point in the schema.
  169. *
  170. * <p>The default value is 'className'.</p>
  171. *
  172. * @param classNameAttribute The name of the attribute used to overload the class name of a bean
  173. * @deprecated 0.5 set the <code>ReadContext</code> property instead
  174. */
  175. public void setClassNameAttribute(String classNameAttribute) {
  176. context.setClassNameAttribute(classNameAttribute);
  177. }
  178. //-------------------------------- Ruleset implementation
  179. /**
  180. * <p>Return namespace associated with this ruleset</p>
  181. *
  182. * <p><strong>Note</strong> namespaces are not currently supported.</p>
  183. *
  184. * @return null
  185. */
  186. public String getNamespaceURI() {
  187. return null;
  188. }
  189. /**
  190. * Add rules for bean to given <code>Digester</code>.
  191. *
  192. * @param digester the <code>Digester</code> to which the rules for the bean will be added
  193. */
  194. public void addRuleInstances(Digester digester) {
  195. if (log.isTraceEnabled()) {
  196. log.trace("Adding rules to:" + digester);
  197. }
  198. context.setDigester(digester);
  199. // if the classloader is not set, set to the digester classloader
  200. if (context.getClassLoader() == null) {
  201. context.setClassLoader(digester.getClassLoader());
  202. }
  203. // TODO: need to think about strategy for paths
  204. // may need to provide a default path and then allow the user to override
  205. digester.addRule("!" + basePath + "/*", new ActionMappingRule());
  206. }
  207. /**
  208. * Single rule that is used to map all elements.
  209. *
  210. * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
  211. */
  212. private final class ActionMappingRule extends Rule {
  213. /**
  214. * Processes the start of a new <code>Element</code>.
  215. * The actual processing is delegated to <code>MappingAction</code>'s.
  216. * @see Rule#begin(String, String, Attributes)
  217. */
  218. public void begin(String namespace, String name, Attributes attributes)
  219. throws Exception {
  220. if (log.isTraceEnabled()) {
  221. int attributesLength = attributes.getLength();
  222. if (attributesLength > 0) {
  223. log.trace("Attributes:");
  224. }
  225. for (int i = 0, size = attributesLength; i < size; i++) {
  226. log.trace("Local:" + attributes.getLocalName(i));
  227. log.trace("URI:" + attributes.getURI(i));
  228. log.trace("QName:" + attributes.getQName(i));
  229. }
  230. }
  231. context.pushElement(name);
  232. MappingAction nextAction =
  233. nextAction(namespace, name, attributes, context);
  234. context.pushMappingAction(nextAction);
  235. }
  236. /**
  237. * Gets the next action to be executed
  238. * @param namespace the element's namespace, not null
  239. * @param name the element name, not null
  240. * @param attributes the element's attributes, not null
  241. * @param context the <code>ReadContext</code> against which the xml is being mapped.
  242. * @return the initialized <code>MappingAction</code>, not null
  243. * @throws Exception
  244. */
  245. private MappingAction nextAction(
  246. String namespace,
  247. String name,
  248. Attributes attributes,
  249. ReadContext context)
  250. throws Exception {
  251. MappingAction result = null;
  252. MappingAction lastAction = context.currentMappingAction();
  253. if (lastAction == null)
  254. {
  255. result = BeanBindAction.INSTANCE;
  256. } else {
  257. result = lastAction.next(namespace, name, attributes, context);
  258. }
  259. return result.begin(namespace, name, attributes, context);
  260. }
  261. /**
  262. * Processes the body text for the current element.
  263. * This is delegated to the current <code>MappingAction</code>.
  264. * @see Rule#body(String, String, String)
  265. */
  266. public void body(String namespace, String name, String text)
  267. throws Exception {
  268. log.trace("[BRS] Body with text " + text);
  269. if (digester.getCount() > 0) {
  270. MappingAction action = context.currentMappingAction();
  271. action.body(text, context);
  272. } else {
  273. log.trace("[BRS] ZERO COUNT");
  274. }
  275. }
  276. /**
  277. * Process the end of this element.
  278. * This is delegated to the current <code>MappingAction</code>.
  279. */
  280. public void end(String namespace, String name) throws Exception {
  281. MappingAction action = context.popMappingAction();
  282. action.end(context);
  283. }
  284. /**
  285. * Tidy up.
  286. */
  287. public void finish() {
  288. //
  289. // Clear indexed beans so that we're ready to process next document
  290. //
  291. context.clearBeans();
  292. }
  293. }
  294. /**
  295. * Specialization of <code>ReadContext</code> when reading from <code>Digester</code>.
  296. * @author <a href='http://jakarta.apache.org/'>Jakarta Commons Team</a>
  297. * @version $Revision: 1.20.2.1 $
  298. */
  299. private static class DigesterReadContext extends ReadContext {
  300. private Digester digester;
  301. /**
  302. * @param context
  303. * @param readConfiguration
  304. */
  305. public DigesterReadContext(
  306. Context context,
  307. ReadConfiguration readConfiguration) {
  308. super(context, readConfiguration);
  309. // TODO Auto-generated constructor stub
  310. }
  311. /**
  312. * @param bindingConfiguration
  313. * @param readConfiguration
  314. */
  315. public DigesterReadContext(
  316. BindingConfiguration bindingConfiguration,
  317. ReadConfiguration readConfiguration) {
  318. super(bindingConfiguration, readConfiguration);
  319. }
  320. /**
  321. * @param log
  322. * @param bindingConfiguration
  323. * @param readConfiguration
  324. */
  325. public DigesterReadContext(
  326. Log log,
  327. BindingConfiguration bindingConfiguration,
  328. ReadConfiguration readConfiguration) {
  329. super(log, bindingConfiguration, readConfiguration);
  330. }
  331. /**
  332. * @param log
  333. * @param bindingConfiguration
  334. * @param readConfiguration
  335. */
  336. public DigesterReadContext(ReadContext readContext) {
  337. super(readContext);
  338. }
  339. public Digester getDigester() {
  340. // TODO: replace with something better
  341. return digester;
  342. }
  343. public void setDigester(Digester digester) {
  344. // TODO: replace once moved to single Rule
  345. this.digester = digester;
  346. }
  347. /* (non-Javadoc)
  348. * @see org.apache.commons.betwixt.io.read.ReadContext#pushBean(java.lang.Object)
  349. */
  350. public void pushBean(Object bean) {
  351. super.pushBean(bean);
  352. digester.push(bean);
  353. }
  354. /* (non-Javadoc)
  355. * @see org.apache.commons.betwixt.io.read.ReadContext#putBean(java.lang.Object)
  356. */
  357. public Object popBean() {
  358. Object bean = super.popBean();
  359. Object top = digester.pop();
  360. return bean;
  361. }
  362. }
  363. }