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