001: /*
002: * Copyright 2005 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.springframework.oxm.castor;
017:
018: import java.io.IOException;
019: import java.io.InputStream;
020: import java.io.OutputStream;
021: import java.io.OutputStreamWriter;
022: import java.io.Reader;
023: import java.io.Writer;
024: import javax.xml.stream.XMLEventReader;
025: import javax.xml.stream.XMLEventWriter;
026: import javax.xml.stream.XMLStreamReader;
027: import javax.xml.stream.XMLStreamWriter;
028:
029: import org.castor.mapping.BindingType;
030: import org.castor.mapping.MappingUnmarshaller;
031: import org.exolab.castor.mapping.Mapping;
032: import org.exolab.castor.mapping.MappingException;
033: import org.exolab.castor.mapping.MappingLoader;
034: import org.exolab.castor.xml.ClassDescriptorResolverFactory;
035: import org.exolab.castor.xml.MarshalException;
036: import org.exolab.castor.xml.Marshaller;
037: import org.exolab.castor.xml.UnmarshalHandler;
038: import org.exolab.castor.xml.Unmarshaller;
039: import org.exolab.castor.xml.XMLClassDescriptorResolver;
040: import org.exolab.castor.xml.XMLException;
041: import org.springframework.beans.factory.InitializingBean;
042: import org.springframework.core.io.Resource;
043: import org.springframework.oxm.AbstractMarshaller;
044: import org.springframework.oxm.XmlMappingException;
045: import org.springframework.util.ObjectUtils;
046: import org.springframework.util.StringUtils;
047: import org.springframework.xml.dom.DomContentHandler;
048: import org.springframework.xml.stream.StaxEventContentHandler;
049: import org.springframework.xml.stream.StaxEventXmlReader;
050: import org.springframework.xml.stream.StaxStreamContentHandler;
051: import org.springframework.xml.stream.StaxStreamXmlReader;
052: import org.w3c.dom.Node;
053: import org.xml.sax.ContentHandler;
054: import org.xml.sax.InputSource;
055: import org.xml.sax.SAXException;
056: import org.xml.sax.XMLReader;
057: import org.xml.sax.ext.LexicalHandler;
058:
059: /**
060: * Implementation of the <code>Marshaller</code> interface for Castor. By default, Castor does not require any further
061: * configuration, though setting a target class or providing a mapping file can be used to have more control over the
062: * behavior of Castor.
063: * <p/>
064: * If a target class is specified using <code>setTargetClass</code>, the <code>CastorMarshaller</code> can only be used
065: * to unmarshall XML that represents that specific class. If you want to unmarshall multiple classes, you have to
066: * provide a mapping file using <code>setMappingLocations</code>.
067: * <p/>
068: * Due to Castor's API, it is required to set the encoding used for writing to output streams. It defaults to
069: * <code>UTF-8</code>.
070: *
071: * @author Arjen Poutsma
072: * @see #setEncoding(String)
073: * @see #setTargetClass(Class)
074: * @see #setMappingLocation(org.springframework.core.io.Resource)
075: * @see #setMappingLocations(org.springframework.core.io.Resource[])
076: * @since 1.0.0
077: */
078: public class CastorMarshaller extends AbstractMarshaller implements
079: InitializingBean {
080:
081: /** The default encoding used for stream access. */
082: public static final String DEFAULT_ENCODING = "UTF-8";
083:
084: private Resource[] mappingLocations;
085:
086: private String encoding = DEFAULT_ENCODING;
087:
088: private Class targetClass;
089:
090: private XMLClassDescriptorResolver classDescriptorResolver;
091:
092: private boolean validating = false;
093:
094: private boolean whitespacePreserve = false;
095:
096: private boolean ignoreExtraAttributes = true;
097:
098: private boolean ignoreExtraElements = false;
099:
100: /** Returns whether the Castor {@link Unmarshaller} should ignore attributes that do not match a specific field. */
101: public boolean getIgnoreExtraAttributes() {
102: return ignoreExtraAttributes;
103: }
104:
105: /**
106: * Sets whether the Castor {@link Unmarshaller} should ignore attributes that do not match a specific field.
107: * Default is <code>true</code>: extra attributes are ignored.
108: */
109: public void setIgnoreExtraAttributes(boolean ignoreExtraAttributes) {
110: this .ignoreExtraAttributes = ignoreExtraAttributes;
111: }
112:
113: /** Returns whether the Castor {@link Unmarshaller} should ignore elements that do not match a specific field. */
114: public boolean getIgnoreExtraElements() {
115: return ignoreExtraElements;
116: }
117:
118: /**
119: * Sets whether the Castor {@link Unmarshaller} should ignore elements that do not match a specific field. Default
120: * is <code>false</code>, extra attributes are flagged as an error.
121: */
122: public void setIgnoreExtraElements(boolean ignoreExtraElements) {
123: this .ignoreExtraElements = ignoreExtraElements;
124: }
125:
126: /** Returns whether the Castor {@link Unmarshaller} should preserve "ignorable" whitespace. */
127: public boolean getWhitespacePreserve() {
128: return whitespacePreserve;
129: }
130:
131: /**
132: * Sets whether the Castor {@link Unmarshaller} should preserve "ignorable" whitespace. Default is
133: * <code>false</code>.
134: */
135: public void setWhitespacePreserve(boolean whitespacePreserve) {
136: this .whitespacePreserve = whitespacePreserve;
137: }
138:
139: /** Returns whether this marshaller should validate in- and outgoing documents. */
140: public boolean isValidating() {
141: return validating;
142: }
143:
144: /** Sets whether this marshaller should validate in- and outgoing documents. Default is <code>false</code>. */
145: public void setValidating(boolean validating) {
146: this .validating = validating;
147: }
148:
149: /**
150: * Sets the encoding to be used for stream access. If this property is not set, the default encoding is used.
151: *
152: * @see #DEFAULT_ENCODING
153: */
154: public void setEncoding(String encoding) {
155: this .encoding = encoding;
156: }
157:
158: /** Sets the locations of the Castor XML Mapping files. */
159: public void setMappingLocation(Resource mappingLocation) {
160: mappingLocations = new Resource[] { mappingLocation };
161: }
162:
163: /** Sets the locations of the Castor XML Mapping files. */
164: public void setMappingLocations(Resource[] mappingLocations) {
165: this .mappingLocations = mappingLocations;
166: }
167:
168: /**
169: * Sets the Castor target class. If this property is set, this <code>CastorMarshaller</code> is tied to this one
170: * specific class. Use a mapping file for unmarshalling multiple classes.
171: * <p/>
172: * You cannot set both this property and the mapping (location).
173: */
174: public void setTargetClass(Class targetClass) {
175: this .targetClass = targetClass;
176: }
177:
178: public final void afterPropertiesSet() throws IOException {
179: if (mappingLocations != null && targetClass != null) {
180: throw new IllegalArgumentException(
181: "Cannot set both the 'mappingLocations' and 'targetClass' property. "
182: + "Set targetClass for unmarshalling a single class, and 'mappingLocations' for multiple classes'");
183: }
184: if (logger.isInfoEnabled()) {
185: if (mappingLocations != null) {
186: logger
187: .info("Configured using "
188: + StringUtils
189: .arrayToCommaDelimitedString(mappingLocations));
190: } else if (targetClass != null) {
191: logger.info("Configured for target class ["
192: + targetClass.getName() + "]");
193: } else {
194: logger.info("Using default configuration");
195: }
196: }
197: try {
198: classDescriptorResolver = createClassDescriptorResolver(
199: mappingLocations, targetClass);
200: } catch (MappingException ex) {
201: throw new CastorSystemException(
202: "Could not load Castor mapping: " + ex.getMessage(),
203: ex);
204: }
205: }
206:
207: /** Returns <code>true</code> for all classes, i.e. Castor supports arbitrary classes. */
208: public boolean supports(Class clazz) {
209: return true;
210: }
211:
212: protected final void marshalDomNode(Object graph, Node node)
213: throws XmlMappingException {
214: marshalSaxHandlers(graph, new DomContentHandler(node), null);
215: }
216:
217: protected final void marshalSaxHandlers(Object graph,
218: ContentHandler contentHandler, LexicalHandler lexicalHandler)
219: throws XmlMappingException {
220: try {
221: marshal(graph, new Marshaller(contentHandler));
222: } catch (IOException ex) {
223: throw new CastorSystemException(
224: "Could not construct Castor ContentHandler Marshaller",
225: ex);
226: }
227: }
228:
229: protected final void marshalOutputStream(Object graph,
230: OutputStream outputStream) throws XmlMappingException,
231: IOException {
232: marshalWriter(graph, new OutputStreamWriter(outputStream,
233: encoding));
234: }
235:
236: protected final void marshalWriter(Object graph, Writer writer)
237: throws XmlMappingException, IOException {
238: marshal(graph, new Marshaller(writer));
239: }
240:
241: protected final void marshalXmlEventWriter(Object graph,
242: XMLEventWriter eventWriter) throws XmlMappingException {
243: marshalSaxHandlers(graph, new StaxEventContentHandler(
244: eventWriter), null);
245: }
246:
247: protected final void marshalXmlStreamWriter(Object graph,
248: XMLStreamWriter streamWriter) throws XmlMappingException {
249: marshalSaxHandlers(graph, new StaxStreamContentHandler(
250: streamWriter), null);
251: }
252:
253: protected final Object unmarshalDomNode(Node node)
254: throws XmlMappingException {
255: try {
256: return createUnmarshaller().unmarshal(node);
257: } catch (XMLException ex) {
258: throw convertCastorException(ex, false);
259: }
260: }
261:
262: protected final Object unmarshalInputStream(InputStream inputStream)
263: throws XmlMappingException, IOException {
264: try {
265: return createUnmarshaller().unmarshal(
266: new InputSource(inputStream));
267: } catch (XMLException ex) {
268: throw convertCastorException(ex, false);
269: }
270: }
271:
272: protected final Object unmarshalReader(Reader reader)
273: throws XmlMappingException, IOException {
274: try {
275: return createUnmarshaller().unmarshal(
276: new InputSource(reader));
277: } catch (XMLException ex) {
278: throw convertCastorException(ex, false);
279: }
280: }
281:
282: protected final Object unmarshalXmlEventReader(
283: XMLEventReader eventReader) {
284: XMLReader reader = new StaxEventXmlReader(eventReader);
285: try {
286: return unmarshalSaxReader(reader, new InputSource());
287: } catch (IOException ex) {
288: throw new CastorUnmarshallingFailureException(
289: new MarshalException(ex));
290: }
291: }
292:
293: protected final Object unmarshalSaxReader(XMLReader xmlReader,
294: InputSource inputSource) throws XmlMappingException,
295: IOException {
296: UnmarshalHandler unmarshalHandler = createUnmarshaller()
297: .createHandler();
298: try {
299: ContentHandler contentHandler = Unmarshaller
300: .getContentHandler(unmarshalHandler);
301: xmlReader.setContentHandler(contentHandler);
302: xmlReader.parse(inputSource);
303: return unmarshalHandler.getObject();
304: } catch (SAXException ex) {
305: throw new CastorUnmarshallingFailureException(ex);
306: }
307: }
308:
309: protected final Object unmarshalXmlStreamReader(
310: XMLStreamReader streamReader) {
311: XMLReader reader = new StaxStreamXmlReader(streamReader);
312: try {
313: return unmarshalSaxReader(reader, new InputSource());
314: } catch (IOException ex) {
315: throw new CastorUnmarshallingFailureException(
316: new MarshalException(ex));
317: }
318: }
319:
320: /**
321: * Creates the Castor <code>XMLClassDescriptorResolver</code>. Subclasses can override this to create a custom
322: * resolver.
323: * <p/>
324: * The default implementation loads mapping files if defined, or loads the target class if not defined.
325: *
326: * @return the created resolver
327: * @throws MappingException when the mapping file cannot be loaded
328: * @throws IOException in case of I/O errors
329: */
330: protected XMLClassDescriptorResolver createClassDescriptorResolver(
331: Resource[] mappingLocations, Class targetClass)
332: throws MappingException, IOException {
333: XMLClassDescriptorResolver classDescriptorResolver = (XMLClassDescriptorResolver) ClassDescriptorResolverFactory
334: .createClassDescriptorResolver(BindingType.XML);
335: if (!ObjectUtils.isEmpty(mappingLocations)) {
336: Mapping mapping = new Mapping();
337: for (int i = 0; i < mappingLocations.length; i++) {
338: mapping.loadMapping(new InputSource(mappingLocations[i]
339: .getInputStream()));
340: }
341: MappingUnmarshaller mappingUnmarshaller = new MappingUnmarshaller();
342: MappingLoader mappingLoader = mappingUnmarshaller
343: .getMappingLoader(mapping, BindingType.XML);
344: classDescriptorResolver.setMappingLoader(mappingLoader);
345: classDescriptorResolver.setClassLoader(mapping
346: .getClassLoader());
347: } else if (targetClass != null) {
348: classDescriptorResolver.setClassLoader(targetClass
349: .getClassLoader());
350: }
351: return classDescriptorResolver;
352: }
353:
354: private Unmarshaller createUnmarshaller() {
355: Unmarshaller unmarshaller = null;
356: if (targetClass != null) {
357: unmarshaller = new Unmarshaller(targetClass);
358: } else {
359: unmarshaller = new Unmarshaller();
360: }
361: unmarshaller.setResolver(classDescriptorResolver);
362: customizeUnmarshaller(unmarshaller);
363: return unmarshaller;
364: }
365:
366: /**
367: * Template method that allows for customizing of the given Castor {@link Unmarshaller}.
368: * <p/>
369: * Default implementation invokes {@link Unmarshaller#setValidation(boolean)}, {@link
370: * Unmarshaller#setWhitespacePreserve(boolean)}, {@link Unmarshaller#setIgnoreExtraAttributes(boolean)}, and {@link
371: * Unmarshaller#setIgnoreExtraElements(boolean)} with the properties set on this marshaller.
372: */
373: protected void customizeUnmarshaller(Unmarshaller unmarshaller) {
374: unmarshaller.setValidation(isValidating());
375: unmarshaller.setWhitespacePreserve(getWhitespacePreserve());
376: unmarshaller
377: .setIgnoreExtraAttributes(getIgnoreExtraAttributes());
378: unmarshaller.setIgnoreExtraElements(getIgnoreExtraElements());
379: }
380:
381: private void marshal(Object graph, Marshaller marshaller) {
382: try {
383: marshaller.setResolver(classDescriptorResolver);
384: customizeMarshaller(marshaller);
385: marshaller.marshal(graph);
386: } catch (XMLException ex) {
387: throw convertCastorException(ex, true);
388: }
389: }
390:
391: /**
392: * Template method that allows for customizing of the given Castor {@link Marshaller}.
393: * <p/>
394: * Default implementation invokes {@link Marshaller#setValidation(boolean)} with the property set on this
395: * marshaller.
396: */
397: protected void customizeMarshaller(Marshaller marshaller) {
398: marshaller.setValidation(isValidating());
399: }
400:
401: /**
402: * Converts the given <code>CastorException</code> to an appropriate exception from the
403: * <code>org.springframework.oxm</code> hierarchy.
404: * <p/>
405: * The default implementation delegates to <code>CastorUtils</code>. Can be overridden in subclasses.
406: * <p/>
407: * A boolean flag is used to indicate whether this exception occurs during marshalling or unmarshalling, since
408: * Castor itself does not make this distinction in its exception hierarchy.
409: *
410: * @param ex Castor <code>XMLException</code> that occured
411: * @param marshalling indicates whether the exception occurs during marshalling (<code>true</code>), or
412: * unmarshalling (<code>false</code>)
413: * @return the corresponding <code>XmlMappingException</code>
414: * @see CastorUtils#convertXmlException
415: */
416: public XmlMappingException convertCastorException(XMLException ex,
417: boolean marshalling) {
418: return CastorUtils.convertXmlException(ex, marshalling);
419: }
420: }
|