001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.betwixt.io;
018:
019: import java.beans.IntrospectionException;
020: import java.io.IOException;
021: import java.util.HashSet;
022: import java.util.Set;
023:
024: import javax.xml.parsers.SAXParser;
025:
026: import org.apache.commons.betwixt.BindingConfiguration;
027: import org.apache.commons.betwixt.ElementDescriptor;
028: import org.apache.commons.betwixt.XMLBeanInfo;
029: import org.apache.commons.betwixt.XMLIntrospector;
030: import org.apache.commons.betwixt.io.read.ReadConfiguration;
031: import org.apache.commons.betwixt.io.read.ReadContext;
032: import org.apache.commons.digester.Digester;
033: import org.apache.commons.digester.ExtendedBaseRules;
034: import org.apache.commons.digester.RuleSet;
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037: import org.xml.sax.InputSource;
038: import org.xml.sax.SAXException;
039: import org.xml.sax.XMLReader;
040:
041: /** <p><code>BeanReader</code> reads a tree of beans from an XML document.</p>
042: *
043: * <p>Call {@link #registerBeanClass(Class)} or {@link #registerBeanClass(String, Class)}
044: * to add rules to map a bean class.</p>
045: *
046: * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
047: */
048: public class BeanReader extends Digester {
049:
050: /** Introspector used */
051: private XMLIntrospector introspector = new XMLIntrospector();
052: /** Log used for logging (Doh!) */
053: private Log log = LogFactory.getLog(BeanReader.class);
054: /** The registered classes */
055: private Set registeredClasses = new HashSet();
056: /** Dynamic binding configuration settings */
057: private BindingConfiguration bindingConfiguration = new BindingConfiguration();
058: /** Reading specific configuration settings */
059: private ReadConfiguration readConfiguration = new ReadConfiguration();
060:
061: /**
062: * Construct a new BeanReader with default properties.
063: */
064: public BeanReader() {
065: // TODO: now we require extended rules may need to document this
066: setRules(new ExtendedBaseRules());
067: }
068:
069: /**
070: * Construct a new BeanReader, allowing a SAXParser to be passed in. This
071: * allows BeanReader to be used in environments which are unfriendly to
072: * JAXP1.1 (such as WebLogic 6.0). Thanks for the request to change go to
073: * James House (james@interobjective.com). This may help in places where
074: * you are able to load JAXP 1.1 classes yourself.
075: *
076: * @param parser use this <code>SAXParser</code>
077: */
078: public BeanReader(SAXParser parser) {
079: super (parser);
080: setRules(new ExtendedBaseRules());
081: }
082:
083: /**
084: * Construct a new BeanReader, allowing an XMLReader to be passed in. This
085: * allows BeanReader to be used in environments which are unfriendly to
086: * JAXP1.1 (such as WebLogic 6.0). Note that if you use this option you
087: * have to configure namespace and validation support yourself, as these
088: * properties only affect the SAXParser and emtpy constructor.
089: *
090: * @param reader use this <code>XMLReader</code> as source for SAX events
091: */
092: public BeanReader(XMLReader reader) {
093: super (reader);
094: setRules(new ExtendedBaseRules());
095: }
096:
097: /**
098: * <p>Register a bean class and add mapping rules for this bean class.</p>
099: *
100: * <p>A bean class is introspected when it is registered.
101: * It will <strong>not</strong> be introspected again even if the introspection
102: * settings are changed.
103: * If re-introspection is required, then {@link #deregisterBeanClass} must be called
104: * and the bean re-registered.</p>
105: *
106: * <p>A bean class can only be registered once.
107: * If the same class is registered a second time, this registration will be ignored.
108: * In order to change a registration, call {@link #deregisterBeanClass}
109: * before calling this method.</p>
110: *
111: * <p>All the rules required to digest this bean are added when this method is called.
112: * Other rules that you want to execute before these should be added before this
113: * method is called.
114: * Those that should be executed afterwards, should be added afterwards.</p>
115: *
116: * @param beanClass the <code>Class</code> to be registered
117: * @throws IntrospectionException if the bean introspection fails
118: */
119: public void registerBeanClass(Class beanClass)
120: throws IntrospectionException {
121: if (!registeredClasses.contains(beanClass)) {
122: register(beanClass, null);
123:
124: } else {
125: if (log.isWarnEnabled()) {
126: log.warn("Cannot add class " + beanClass.getName()
127: + " since it already exists");
128: }
129: }
130: }
131:
132: /**
133: * Registers the given class at the given path.
134: * @param beanClass <code>Class</code> for binding
135: * @param path the path at which the bean class should be registered
136: * or null if the automatic path is to be used
137: * @throws IntrospectionException
138: */
139: private void register(Class beanClass, String path)
140: throws IntrospectionException {
141: if (log.isTraceEnabled()) {
142: log.trace("Registering class " + beanClass);
143: }
144: XMLBeanInfo xmlInfo = introspector.introspect(beanClass);
145: registeredClasses.add(beanClass);
146:
147: ElementDescriptor elementDescriptor = xmlInfo
148: .getElementDescriptor();
149:
150: if (path == null) {
151: path = elementDescriptor.getQualifiedName();
152: }
153:
154: if (log.isTraceEnabled()) {
155: log.trace("Added path: " + path + ", mapped to: "
156: + beanClass.getName());
157: }
158: addBeanCreateRule(path, elementDescriptor, beanClass);
159: }
160:
161: /**
162: * <p>Registers a bean class
163: * and add mapping rules for this bean class at the given path expression.</p>
164: *
165: *
166: * <p>A bean class is introspected when it is registered.
167: * It will <strong>not</strong> be introspected again even if the introspection
168: * settings are changed.
169: * If re-introspection is required, then {@link #deregisterBeanClass} must be called
170: * and the bean re-registered.</p>
171: *
172: * <p>A bean class can only be registered once.
173: * If the same class is registered a second time, this registration will be ignored.
174: * In order to change a registration, call {@link #deregisterBeanClass}
175: * before calling this method.</p>
176: *
177: * <p>All the rules required to digest this bean are added when this method is called.
178: * Other rules that you want to execute before these should be added before this
179: * method is called.
180: * Those that should be executed afterwards, should be added afterwards.</p>
181: *
182: * @param path the xml path expression where the class is to registered.
183: * This should be in digester path notation
184: * @param beanClass the <code>Class</code> to be registered
185: * @throws IntrospectionException if the bean introspection fails
186: */
187: public void registerBeanClass(String path, Class beanClass)
188: throws IntrospectionException {
189: if (!registeredClasses.contains(beanClass)) {
190:
191: register(beanClass, path);
192:
193: } else {
194: if (log.isWarnEnabled()) {
195: log.warn("Cannot add class " + beanClass.getName()
196: + " since it already exists");
197: }
198: }
199: }
200:
201: /**
202: * <p>Registers a class with a multi-mapping.
203: * This mapping is specified by the multi-mapping document
204: * contained in the given <code>InputSource</code>.
205: * </p><p>
206: * <strong>Note:</strong> the custom mappings will be registered with
207: * the introspector. This must remain so for the reading to work correctly
208: * It is recommended that use of the pre-registeration process provided
209: * by {@link XMLIntrospector#register} be considered as an alternative to this method.
210: * </p>
211: * @see #registerBeanClass(Class) since the general notes given there
212: * apply equally to this
213: * @see XMLIntrospector#register(InputSource) for more details on the multi-mapping format
214: * @since 0.7
215: * @param mapping <code>InputSource</code> giving the multi-mapping document specifying
216: * the mapping
217: * @throws IntrospectionException
218: * @throws SAXException
219: * @throws IOException
220: */
221: public void registerMultiMapping(InputSource mapping)
222: throws IntrospectionException, IOException, SAXException {
223: Class[] mappedClasses = introspector.register(mapping);
224: for (int i = 0, size = mappedClasses.length; i < size; i++) {
225: Class beanClass = mappedClasses[i];
226: if (!registeredClasses.contains(beanClass)) {
227: register(beanClass, null);
228:
229: }
230: }
231: }
232:
233: /**
234: * <p>Registers a class with a custom mapping.
235: * This mapping is specified by the standard dot betwixt document
236: * contained in the given <code>InputSource</code>.
237: * </p><p>
238: * <strong>Note:</strong> the custom mapping will be registered with
239: * the introspector. This must remain so for the reading to work correctly
240: * It is recommended that use of the pre-registeration process provided
241: * by {@link XMLIntrospector#register} be considered as an alternative to this method.
242: * </p>
243: * @see #registerBeanClass(Class) since the general notes given there
244: * apply equally to this
245: * @since 0.7
246: * @param mapping <code>InputSource</code> giving the dot betwixt document specifying
247: * the mapping
248: * @param beanClass <code>Class</code> that should be register
249: * @throws IntrospectionException
250: * @throws SAXException
251: * @throws IOException
252: */
253: public void registerBeanClass(InputSource mapping, Class beanClass)
254: throws IntrospectionException, IOException, SAXException {
255: if (!registeredClasses.contains(beanClass)) {
256:
257: introspector.register(beanClass, mapping);
258: register(beanClass, null);
259:
260: } else {
261: if (log.isWarnEnabled()) {
262: log.warn("Cannot add class " + beanClass.getName()
263: + " since it already exists");
264: }
265: }
266: }
267:
268: /**
269: * <p>Flush all registered bean classes.
270: * This allows all bean classes to be re-registered
271: * by a subsequent calls to <code>registerBeanClass</code>.</p>
272: *
273: * <p><strong>Note</strong> that deregistering a bean does <strong>not</strong>
274: * remove the Digester rules associated with that bean.</p>
275: * @since 0.5
276: */
277: public void flushRegisteredBeanClasses() {
278: registeredClasses.clear();
279: }
280:
281: /**
282: * <p>Remove the given class from the register.
283: * Calling this method will allow the bean class to be re-registered
284: * by a subsequent call to <code>registerBeanClass</code>.
285: * This allows (for example) a bean to be reintrospected after a change
286: * to the introspection settings.</p>
287: *
288: * <p><strong>Note</strong> that deregistering a bean does <strong>not</strong>
289: * remove the Digester rules associated with that bean.</p>
290: *
291: * @param beanClass the <code>Class</code> to remove from the set of registered bean classes
292: * @since 0.5
293: */
294: public void deregisterBeanClass(Class beanClass) {
295: registeredClasses.remove(beanClass);
296: }
297:
298: // Properties
299: //-------------------------------------------------------------------------
300:
301: /**
302: * <p> Get the introspector used. </p>
303: *
304: * <p> The {@link XMLBeanInfo} used to map each bean is
305: * created by the <code>XMLIntrospector</code>.
306: * One way in which the mapping can be customized is by
307: * altering the <code>XMLIntrospector</code>. </p>
308: *
309: * @return the <code>XMLIntrospector</code> used for the introspection
310: */
311: public XMLIntrospector getXMLIntrospector() {
312: return introspector;
313: }
314:
315: /**
316: * <p> Set the introspector to be used. </p>
317: *
318: * <p> The {@link XMLBeanInfo} used to map each bean is
319: * created by the <code>XMLIntrospector</code>.
320: * One way in which the mapping can be customized is by
321: * altering the <code>XMLIntrospector</code>. </p>
322: *
323: * @param introspector use this introspector
324: */
325: public void setXMLIntrospector(XMLIntrospector introspector) {
326: this .introspector = introspector;
327: }
328:
329: /**
330: * <p> Get the current level for logging. </p>
331: *
332: * @return the <code>Log</code> implementation this class logs to
333: */
334: public Log getLog() {
335: return log;
336: }
337:
338: /**
339: * <p> Set the current logging level. </p>
340: *
341: * @param log the <code>Log</code>implementation to use for logging
342: */
343: public void setLog(Log log) {
344: this .log = log;
345: setLogger(log);
346: }
347:
348: /**
349: * Should the reader use <code>ID</code> attributes to match beans.
350: *
351: * @return true if <code>ID</code> and <code>IDREF</code>
352: * attributes should be used to match instances
353: * @deprecated 0.5 use {@link BindingConfiguration#getMapIDs}
354: */
355: public boolean getMatchIDs() {
356: return getBindingConfiguration().getMapIDs();
357: }
358:
359: /**
360: * Set whether the read should use <code>ID</code> attributes to match beans.
361: *
362: * @param matchIDs pass true if <code>ID</code>'s should be matched
363: * @deprecated 0.5 use {@link BindingConfiguration#setMapIDs}
364: */
365: public void setMatchIDs(boolean matchIDs) {
366: getBindingConfiguration().setMapIDs(matchIDs);
367: }
368:
369: /**
370: * Gets the dynamic configuration setting to be used for bean reading.
371: * @return the BindingConfiguration settings, not null
372: * @since 0.5
373: */
374: public BindingConfiguration getBindingConfiguration() {
375: return bindingConfiguration;
376: }
377:
378: /**
379: * Sets the dynamic configuration setting to be used for bean reading.
380: * @param bindingConfiguration the BindingConfiguration settings, not null
381: * @since 0.5
382: */
383: public void setBindingConfiguration(
384: BindingConfiguration bindingConfiguration) {
385: this .bindingConfiguration = bindingConfiguration;
386: }
387:
388: /**
389: * Gets read specific configuration details.
390: * @return the ReadConfiguration, not null
391: * @since 0.5
392: */
393: public ReadConfiguration getReadConfiguration() {
394: return readConfiguration;
395: }
396:
397: /**
398: * Sets the read specific configuration details.
399: * @param readConfiguration not null
400: * @since 0.5
401: */
402: public void setReadConfiguration(ReadConfiguration readConfiguration) {
403: this .readConfiguration = readConfiguration;
404: }
405:
406: // Implementation methods
407: //-------------------------------------------------------------------------
408:
409: /**
410: * Adds a new bean create rule for the specified path
411: *
412: * @param path the digester path at which this rule should be added
413: * @param elementDescriptor the <code>ElementDescriptor</code> describes the expected element
414: * @param beanClass the <code>Class</code> of the bean created by this rule
415: */
416: protected void addBeanCreateRule(String path,
417: ElementDescriptor elementDescriptor, Class beanClass) {
418: if (log.isTraceEnabled()) {
419: log.trace("Adding BeanRuleSet for " + beanClass);
420: }
421: RuleSet ruleSet = new BeanRuleSet(introspector, path,
422: elementDescriptor, beanClass, makeContext());
423: addRuleSet(ruleSet);
424: }
425:
426: /**
427: * Factory method for new contexts.
428: * Ensure that they are correctly configured.
429: * @return the ReadContext created, not null
430: */
431: private ReadContext makeContext() {
432: return new ReadContext(log, bindingConfiguration,
433: readConfiguration);
434: }
435: }
|