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 java.beans.IntrospectionException;
  18. import java.util.HashSet;
  19. import java.util.Set;
  20. import javax.xml.parsers.SAXParser;
  21. import org.apache.commons.betwixt.BindingConfiguration;
  22. import org.apache.commons.betwixt.ElementDescriptor;
  23. import org.apache.commons.betwixt.XMLBeanInfo;
  24. import org.apache.commons.betwixt.XMLIntrospector;
  25. import org.apache.commons.betwixt.io.read.ReadConfiguration;
  26. import org.apache.commons.betwixt.io.read.ReadContext;
  27. import org.apache.commons.digester.Digester;
  28. import org.apache.commons.digester.ExtendedBaseRules;
  29. import org.apache.commons.digester.RuleSet;
  30. import org.apache.commons.logging.Log;
  31. import org.apache.commons.logging.LogFactory;
  32. import org.xml.sax.XMLReader;
  33. /** <p><code>BeanReader</code> reads a tree of beans from an XML document.</p>
  34. *
  35. * <p>Call {@link #registerBeanClass(Class)} or {@link #registerBeanClass(String, Class)}
  36. * to add rules to map a bean class.</p>
  37. *
  38. * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
  39. */
  40. public class BeanReader extends Digester {
  41. /** Introspector used */
  42. private XMLIntrospector introspector = new XMLIntrospector();
  43. /** Log used for logging (Doh!) */
  44. private Log log = LogFactory.getLog( BeanReader.class );
  45. /** The registered classes */
  46. private Set registeredClasses = new HashSet();
  47. /** Dynamic binding configuration settings */
  48. private BindingConfiguration bindingConfiguration = new BindingConfiguration();
  49. /** Reading specific configuration settings */
  50. private ReadConfiguration readConfiguration = new ReadConfiguration();
  51. /**
  52. * Construct a new BeanReader with default properties.
  53. */
  54. public BeanReader() {
  55. // TODO: now we require extended rules may need to document this
  56. setRules(new ExtendedBaseRules());
  57. }
  58. /**
  59. * Construct a new BeanReader, allowing a SAXParser to be passed in. This
  60. * allows BeanReader to be used in environments which are unfriendly to
  61. * JAXP1.1 (such as WebLogic 6.0). Thanks for the request to change go to
  62. * James House (james@interobjective.com). This may help in places where
  63. * you are able to load JAXP 1.1 classes yourself.
  64. *
  65. * @param parser use this <code>SAXParser</code>
  66. */
  67. public BeanReader(SAXParser parser) {
  68. super(parser);
  69. setRules(new ExtendedBaseRules());
  70. }
  71. /**
  72. * Construct a new BeanReader, allowing an XMLReader to be passed in. This
  73. * allows BeanReader to be used in environments which are unfriendly to
  74. * JAXP1.1 (such as WebLogic 6.0). Note that if you use this option you
  75. * have to configure namespace and validation support yourself, as these
  76. * properties only affect the SAXParser and emtpy constructor.
  77. *
  78. * @param reader use this <code>XMLReader</code> as source for SAX events
  79. */
  80. public BeanReader(XMLReader reader) {
  81. super(reader);
  82. setRules(new ExtendedBaseRules());
  83. }
  84. /**
  85. * <p>Register a bean class and add mapping rules for this bean class.</p>
  86. *
  87. * <p>A bean class is introspected when it is registered.
  88. * It will <strong>not</strong> be introspected again even if the introspection
  89. * settings are changed.
  90. * If re-introspection is required, then {@link #deregisterBeanClass} must be called
  91. * and the bean re-registered.</p>
  92. *
  93. * <p>A bean class can only be registered once.
  94. * If the same class is registered a second time, this registration will be ignored.
  95. * In order to change a registration, call {@link #deregisterBeanClass}
  96. * before calling this method.</p>
  97. *
  98. * <p>All the rules required to digest this bean are added when this method is called.
  99. * Other rules that you want to execute before these should be added before this
  100. * method is called.
  101. * Those that should be executed afterwards, should be added afterwards.</p>
  102. *
  103. * @param beanClass the <code>Class</code> to be registered
  104. * @throws IntrospectionException if the bean introspection fails
  105. */
  106. public void registerBeanClass(Class beanClass) throws IntrospectionException {
  107. if ( ! registeredClasses.contains( beanClass ) ) {
  108. if ( log.isTraceEnabled() ) {
  109. log.trace( "Registering class " + beanClass );
  110. }
  111. registeredClasses.add( beanClass );
  112. // introspect and find the ElementDescriptor to use as the root
  113. XMLBeanInfo xmlInfo = introspector.introspect( beanClass );
  114. ElementDescriptor elementDescriptor = xmlInfo.getElementDescriptor();
  115. String path = elementDescriptor.getQualifiedName();
  116. if (log.isTraceEnabled()) {
  117. log.trace("Added path: " + path + ", mapped to: " + beanClass.getName());
  118. }
  119. addBeanCreateRule( path, elementDescriptor, beanClass );
  120. } else {
  121. if ( log.isWarnEnabled() ) {
  122. log.warn("Cannot add class " + beanClass.getName() + " since it already exists");
  123. }
  124. }
  125. }
  126. /**
  127. * <p>Registers a bean class
  128. * and add mapping rules for this bean class at the given path expression.</p>
  129. *
  130. *
  131. * <p>A bean class is introspected when it is registered.
  132. * It will <strong>not</strong> be introspected again even if the introspection
  133. * settings are changed.
  134. * If re-introspection is required, then {@link #deregisterBeanClass} must be called
  135. * and the bean re-registered.</p>
  136. *
  137. * <p>A bean class can only be registered once.
  138. * If the same class is registered a second time, this registration will be ignored.
  139. * In order to change a registration, call {@link #deregisterBeanClass}
  140. * before calling this method.</p>
  141. *
  142. * <p>All the rules required to digest this bean are added when this method is called.
  143. * Other rules that you want to execute before these should be added before this
  144. * method is called.
  145. * Those that should be executed afterwards, should be added afterwards.</p>
  146. *
  147. * @param path the xml path expression where the class is to registered.
  148. * This should be in digester path notation
  149. * @param beanClass the <code>Class</code> to be registered
  150. * @throws IntrospectionException if the bean introspection fails
  151. */
  152. public void registerBeanClass(String path, Class beanClass) throws IntrospectionException {
  153. if ( ! registeredClasses.contains( beanClass ) ) {
  154. registeredClasses.add( beanClass );
  155. // introspect and find the ElementDescriptor to use as the root
  156. XMLBeanInfo xmlInfo = introspector.introspect( beanClass );
  157. ElementDescriptor elementDescriptor = xmlInfo.getElementDescriptor();
  158. addBeanCreateRule( path, elementDescriptor, beanClass );
  159. } else {
  160. if ( log.isWarnEnabled() ) {
  161. log.warn("Cannot add class " + beanClass.getName() + " since it already exists");
  162. }
  163. }
  164. }
  165. /**
  166. * <p>Flush all registered bean classes.
  167. * This allows all bean classes to be re-registered
  168. * by a subsequent calls to <code>registerBeanClass</code>.</p>
  169. *
  170. * <p><strong>Note</strong> that deregistering a bean does <strong>not</strong>
  171. * remove the Digester rules associated with that bean.</p>
  172. * @since 0.5
  173. */
  174. public void flushRegisteredBeanClasses() {
  175. registeredClasses.clear();
  176. }
  177. /**
  178. * <p>Remove the given class from the register.
  179. * Calling this method will allow the bean class to be re-registered
  180. * by a subsequent call to <code>registerBeanClass</code>.
  181. * This allows (for example) a bean to be reintrospected after a change
  182. * to the introspection settings.</p>
  183. *
  184. * <p><strong>Note</strong> that deregistering a bean does <strong>not</strong>
  185. * remove the Digester rules associated with that bean.</p>
  186. *
  187. * @param beanClass the <code>Class</code> to remove from the set of registered bean classes
  188. * @since 0.5
  189. */
  190. public void deregisterBeanClass( Class beanClass ) {
  191. registeredClasses.remove( beanClass );
  192. }
  193. // Properties
  194. //-------------------------------------------------------------------------
  195. /**
  196. * <p> Get the introspector used. </p>
  197. *
  198. * <p> The {@link XMLBeanInfo} used to map each bean is
  199. * created by the <code>XMLIntrospector</code>.
  200. * One way in which the mapping can be customized is by
  201. * altering the <code>XMLIntrospector</code>. </p>
  202. *
  203. * @return the <code>XMLIntrospector</code> used for the introspection
  204. */
  205. public XMLIntrospector getXMLIntrospector() {
  206. return introspector;
  207. }
  208. /**
  209. * <p> Set the introspector to be used. </p>
  210. *
  211. * <p> The {@link XMLBeanInfo} used to map each bean is
  212. * created by the <code>XMLIntrospector</code>.
  213. * One way in which the mapping can be customized is by
  214. * altering the <code>XMLIntrospector</code>. </p>
  215. *
  216. * @param introspector use this introspector
  217. */
  218. public void setXMLIntrospector(XMLIntrospector introspector) {
  219. this.introspector = introspector;
  220. }
  221. /**
  222. * <p> Get the current level for logging. </p>
  223. *
  224. * @return the <code>Log</code> implementation this class logs to
  225. */
  226. public Log getLog() {
  227. return log;
  228. }
  229. /**
  230. * <p> Set the current logging level. </p>
  231. *
  232. * @param log the <code>Log</code>implementation to use for logging
  233. */
  234. public void setLog(Log log) {
  235. this.log = log;
  236. setLogger(log);
  237. }
  238. /**
  239. * Should the reader use <code>ID</code> attributes to match beans.
  240. *
  241. * @return true if <code>ID</code> and <code>IDREF</code>
  242. * attributes should be used to match instances
  243. * @deprecated 0.5 use {@link BindingConfiguration#getMapIDs}
  244. */
  245. public boolean getMatchIDs() {
  246. return getBindingConfiguration().getMapIDs();
  247. }
  248. /**
  249. * Set whether the read should use <code>ID</code> attributes to match beans.
  250. *
  251. * @param matchIDs pass true if <code>ID</code>'s should be matched
  252. * @deprecated 0.5 use {@link BindingConfiguration#setMapIDs}
  253. */
  254. public void setMatchIDs(boolean matchIDs) {
  255. getBindingConfiguration().setMapIDs( matchIDs );
  256. }
  257. /**
  258. * Gets the dynamic configuration setting to be used for bean reading.
  259. * @return the BindingConfiguration settings, not null
  260. * @since 0.5
  261. */
  262. public BindingConfiguration getBindingConfiguration() {
  263. return bindingConfiguration;
  264. }
  265. /**
  266. * Sets the dynamic configuration setting to be used for bean reading.
  267. * @param bindingConfiguration the BindingConfiguration settings, not null
  268. * @since 0.5
  269. */
  270. public void setBindingConfiguration( BindingConfiguration bindingConfiguration ) {
  271. this.bindingConfiguration = bindingConfiguration;
  272. }
  273. /**
  274. * Gets read specific configuration details.
  275. * @return the ReadConfiguration, not null
  276. * @since 0.5
  277. */
  278. public ReadConfiguration getReadConfiguration() {
  279. return readConfiguration;
  280. }
  281. /**
  282. * Sets the read specific configuration details.
  283. * @param readConfiguration not null
  284. * @since 0.5
  285. */
  286. public void setReadConfiguration( ReadConfiguration readConfiguration ) {
  287. this.readConfiguration = readConfiguration;
  288. }
  289. // Implementation methods
  290. //-------------------------------------------------------------------------
  291. /**
  292. * Adds a new bean create rule for the specified path
  293. *
  294. * @param path the digester path at which this rule should be added
  295. * @param elementDescriptor the <code>ElementDescriptor</code> describes the expected element
  296. * @param beanClass the <code>Class</code> of the bean created by this rule
  297. */
  298. protected void addBeanCreateRule(
  299. String path,
  300. ElementDescriptor elementDescriptor,
  301. Class beanClass ) {
  302. if (log.isTraceEnabled()) {
  303. log.trace("Adding BeanRuleSet for " + beanClass);
  304. }
  305. RuleSet ruleSet = new BeanRuleSet(
  306. introspector,
  307. path ,
  308. elementDescriptor,
  309. beanClass,
  310. makeContext());
  311. addRuleSet( ruleSet );
  312. }
  313. /**
  314. * Factory method for new contexts.
  315. * Ensure that they are correctly configured.
  316. * @return the ReadContext created, not null
  317. */
  318. private ReadContext makeContext() {
  319. return new ReadContext( log, bindingConfiguration, readConfiguration );
  320. }
  321. }