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