View Javadoc

1   /*
2    * Copyright 2006 the original author or authors.
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  
17  package org.springframework.oxm;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.io.Reader;
23  import java.io.Writer;
24  import javax.xml.parsers.DocumentBuilder;
25  import javax.xml.parsers.DocumentBuilderFactory;
26  import javax.xml.parsers.ParserConfigurationException;
27  import javax.xml.stream.XMLEventReader;
28  import javax.xml.stream.XMLEventWriter;
29  import javax.xml.stream.XMLStreamReader;
30  import javax.xml.stream.XMLStreamWriter;
31  import javax.xml.transform.Result;
32  import javax.xml.transform.Source;
33  import javax.xml.transform.dom.DOMResult;
34  import javax.xml.transform.dom.DOMSource;
35  import javax.xml.transform.sax.SAXResult;
36  import javax.xml.transform.sax.SAXSource;
37  import javax.xml.transform.stax.StAXSource;
38  import javax.xml.transform.stream.StreamResult;
39  import javax.xml.transform.stream.StreamSource;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.w3c.dom.Node;
44  import org.xml.sax.ContentHandler;
45  import org.xml.sax.InputSource;
46  import org.xml.sax.SAXException;
47  import org.xml.sax.XMLReader;
48  import org.xml.sax.ext.LexicalHandler;
49  import org.xml.sax.helpers.XMLReaderFactory;
50  
51  import org.springframework.util.Assert;
52  import org.springframework.xml.transform.StaxSource;
53  import org.springframework.xml.transform.TraxUtils;
54  
55  /**
56   * Abstract implementation of the <code>Marshaller</code> and <code>Unmarshaller</code> interface. This implementation
57   * inspects the given <code>Source</code> or <code>Result</code>, and defers further handling to overridable template
58   * methods.
59   *
60   * @author Arjen Poutsma
61   * @since 1.0.0
62   */
63  public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
64  
65      /** Logger available to subclasses. */
66      protected final Log logger = LogFactory.getLog(getClass());
67  
68      private DocumentBuilderFactory documentBuilderFactory;
69  
70      /**
71       * Marshals the object graph with the given root into the provided <code>javax.xml.transform.Result</code>.
72       * <p/>
73       * This implementation inspects the given result, and calls <code>marshalDomResult</code>,
74       * <code>marshalSaxResult</code>, or <code>marshalStreamResult</code>.
75       *
76       * @param graph  the root of the object graph to marshal
77       * @param result the result to marshal to
78       * @throws XmlMappingException      if the given object cannot be marshalled to the result
79       * @throws IOException              if an I/O exception occurs
80       * @throws IllegalArgumentException if <code>result</code> if neither a <code>DOMResult</code>,
81       *                                  <code>SAXResult</code>, <code>StreamResult</code>
82       * @see #marshalDomResult(Object,javax.xml.transform.dom.DOMResult)
83       * @see #marshalSaxResult(Object,javax.xml.transform.sax.SAXResult)
84       * @see #marshalStreamResult(Object,javax.xml.transform.stream.StreamResult)
85       */
86      public final void marshal(Object graph, Result result) throws XmlMappingException, IOException {
87          if (result instanceof DOMResult) {
88              marshalDomResult(graph, (DOMResult) result);
89          }
90          else if (TraxUtils.isStaxResult(result)) {
91              marshalStaxResult(graph, result);
92          }
93          else if (result instanceof SAXResult) {
94              marshalSaxResult(graph, (SAXResult) result);
95          }
96          else if (result instanceof StreamResult) {
97              marshalStreamResult(graph, (StreamResult) result);
98          }
99          else {
100             throw new IllegalArgumentException("Unknown Result type: " + result.getClass());
101         }
102     }
103 
104     /**
105      * Unmarshals the given provided <code>javax.xml.transform.Source</code> into an object graph.
106      * <p/>
107      * This implementation inspects the given result, and calls <code>unmarshalDomSource</code>,
108      * <code>unmarshalSaxSource</code>, or <code>unmarshalStreamSource</code>.
109      *
110      * @param source the source to marshal from
111      * @return the object graph
112      * @throws XmlMappingException      if the given source cannot be mapped to an object
113      * @throws IOException              if an I/O Exception occurs
114      * @throws IllegalArgumentException if <code>source</code> is neither a <code>DOMSource</code>, a
115      *                                  <code>SAXSource</code>, nor a <code>StreamSource</code>
116      * @see #unmarshalDomSource(javax.xml.transform.dom.DOMSource)
117      * @see #unmarshalSaxSource(javax.xml.transform.sax.SAXSource)
118      * @see #unmarshalStreamSource(javax.xml.transform.stream.StreamSource)
119      */
120     public final Object unmarshal(Source source) throws XmlMappingException, IOException {
121         if (source instanceof DOMSource) {
122             return unmarshalDomSource((DOMSource) source);
123         }
124         else if (TraxUtils.isStaxSource(source)) {
125             return unmarshalStaxSource(source);
126         }
127         else if (source instanceof SAXSource) {
128             return unmarshalSaxSource((SAXSource) source);
129         }
130         else if (source instanceof StreamSource) {
131             return unmarshalStreamSource((StreamSource) source);
132         }
133         else {
134             throw new IllegalArgumentException("Unknown Source type: " + source.getClass());
135         }
136     }
137 
138     /**
139      * Create a <code>DocumentBuilder</code> that this marshaller will use for creating DOM documents when passed an
140      * empty <code>DOMSource</code>. Can be overridden in subclasses, adding further initialization of the builder.
141      *
142      * @param factory the <code>DocumentBuilderFactory</code> that the DocumentBuilder should be created with
143      * @return the <code>DocumentBuilder</code>
144      * @throws javax.xml.parsers.ParserConfigurationException
145      *          if thrown by JAXP methods
146      */
147     protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory)
148             throws ParserConfigurationException {
149         return factory.newDocumentBuilder();
150     }
151 
152     /**
153      * Create a <code>DocumentBuilder</code> that this marshaller will use for creating DOM documents when passed an
154      * empty <code>DOMSource</code>. The resulting <code>DocumentBuilderFactory</code> is cached, so this method will
155      * only be called once.
156      *
157      * @return the DocumentBuilderFactory
158      * @throws ParserConfigurationException if thrown by JAXP methods
159      */
160     protected DocumentBuilderFactory createDocumentBuilderFactory() throws ParserConfigurationException {
161         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
162         factory.setValidating(false);
163         factory.setNamespaceAware(true);
164         return factory;
165     }
166 
167     /**
168      * Create a <code>XMLReader</code> that this marshaller will when passed an empty <code>SAXSource</code>.
169      *
170      * @return the XMLReader
171      * @throws SAXException if thrown by JAXP methods
172      */
173     protected XMLReader createXmlReader() throws SAXException {
174         return XMLReaderFactory.createXMLReader();
175     }
176 
177     //
178     // Marshalling
179     //
180 
181     /**
182      * Template method for handling <code>DOMResult</code>s. This implementation defers to <code>marshalDomNode</code>.
183      *
184      * @param graph     the root of the object graph to marshal
185      * @param domResult the <code>DOMResult</code>
186      * @throws XmlMappingException      if the given object cannot be marshalled to the result
187      * @throws IllegalArgumentException if the <code>domResult</code> is empty
188      * @see #marshalDomNode(Object,org.w3c.dom.Node)
189      */
190     protected void marshalDomResult(Object graph, DOMResult domResult) throws XmlMappingException {
191         Assert.notNull(domResult.getNode(), "DOMResult does not contain Node");
192         marshalDomNode(graph, domResult.getNode());
193     }
194 
195     /**
196      * Template method for handling <code>StaxResult</code>s.  This implementation defers to
197      * <code>marshalXMLSteamWriter</code>, or <code>marshalXMLEventConsumer</code>, depending on what is contained in
198      * the <code>StaxResult</code>.
199      *
200      * @param graph      the root of the object graph to marshal
201      * @param staxResult a Spring-WS {@link StaxSource} or JAXP 1.4 {@link StAXSource}
202      * @throws XmlMappingException      if the given object cannot be marshalled to the result
203      * @throws IllegalArgumentException if the <code>domResult</code> is empty
204      * @see #marshalDomNode(Object,org.w3c.dom.Node)
205      */
206     protected void marshalStaxResult(Object graph, Result staxResult) throws XmlMappingException {
207         XMLStreamWriter streamWriter = TraxUtils.getXMLStreamWriter(staxResult);
208         if (streamWriter != null) {
209             marshalXmlStreamWriter(graph, streamWriter);
210         }
211         else {
212             XMLEventWriter eventWriter = TraxUtils.getXMLEventWriter(staxResult);
213             if (eventWriter != null) {
214                 marshalXmlEventWriter(graph, eventWriter);
215             }
216             else {
217                 throw new IllegalArgumentException("StaxResult contains neither XMLStreamWriter nor XMLEventConsumer");
218             }
219         }
220     }
221 
222     /**
223      * Template method for handling <code>SAXResult</code>s. This implementation defers to
224      * <code>marshalSaxHandlers</code>.
225      *
226      * @param graph     the root of the object graph to marshal
227      * @param saxResult the <code>SAXResult</code>
228      * @throws XmlMappingException if the given object cannot be marshalled to the result
229      * @see #marshalSaxHandlers(Object,org.xml.sax.ContentHandler,org.xml.sax.ext.LexicalHandler)
230      */
231     protected void marshalSaxResult(Object graph, SAXResult saxResult) throws XmlMappingException {
232         ContentHandler contentHandler = saxResult.getHandler();
233         Assert.notNull(contentHandler, "ContentHandler not set on SAXResult");
234         LexicalHandler lexicalHandler = saxResult.getLexicalHandler();
235         marshalSaxHandlers(graph, contentHandler, lexicalHandler);
236     }
237 
238     /**
239      * Template method for handling <code>StreamResult</code>s. This implementation defers to
240      * <code>marshalOutputStream</code>, or <code>marshalWriter</code>, depending on what is contained in the
241      * <code>StreamResult</code>
242      *
243      * @param graph        the root of the object graph to marshal
244      * @param streamResult the <code>StreamResult</code>
245      * @throws IOException              if an I/O Exception occurs
246      * @throws XmlMappingException      if the given object cannot be marshalled to the result
247      * @throws IllegalArgumentException if <code>streamResult</code> contains neither <code>OutputStream</code> nor
248      *                                  <code>Writer</code>.
249      */
250     protected void marshalStreamResult(Object graph, StreamResult streamResult)
251             throws XmlMappingException, IOException {
252         if (streamResult.getOutputStream() != null) {
253             marshalOutputStream(graph, streamResult.getOutputStream());
254         }
255         else if (streamResult.getWriter() != null) {
256             marshalWriter(graph, streamResult.getWriter());
257         }
258         else {
259             throw new IllegalArgumentException("StreamResult contains neither OutputStream nor Writer");
260         }
261     }
262 
263     //
264     // Unmarshalling
265     //
266 
267     /**
268      * Template method for handling <code>DOMSource</code>s. This implementation defers to
269      * <code>unmarshalDomNode</code>. If the given source is empty, an empty source <code>Document</code> will be
270      * created as a placeholder.
271      *
272      * @param domSource the <code>DOMSource</code>
273      * @return the object graph
274      * @throws IllegalArgumentException if the <code>domSource</code> is empty
275      * @throws XmlMappingException      if the given source cannot be mapped to an object
276      * @see #unmarshalDomNode(org.w3c.dom.Node)
277      */
278     protected Object unmarshalDomSource(DOMSource domSource) throws XmlMappingException {
279         if (domSource.getNode() == null) {
280             try {
281                 if (documentBuilderFactory == null) {
282                     documentBuilderFactory = createDocumentBuilderFactory();
283                 }
284                 DocumentBuilder documentBuilder = createDocumentBuilder(documentBuilderFactory);
285                 domSource.setNode(documentBuilder.newDocument());
286             }
287             catch (ParserConfigurationException ex) {
288                 throw new UnmarshallingFailureException(
289                         "Could not create document placeholder for DOMSource: " + ex.getMessage(), ex);
290             }
291         }
292         return unmarshalDomNode(domSource.getNode());
293     }
294 
295     /**
296      * Template method for handling <code>StaxSource</code>s. This implementation defers to
297      * <code>unmarshalXmlStreamReader</code>, or <code>unmarshalXmlEventReader</code>.
298      *
299      * @param staxSource the <code>StaxSource</code>
300      * @return the object graph
301      * @throws XmlMappingException if the given source cannot be mapped to an object
302      */
303     protected Object unmarshalStaxSource(Source staxSource) throws XmlMappingException {
304         XMLStreamReader streamReader = TraxUtils.getXMLStreamReader(staxSource);
305         if (streamReader != null) {
306             return unmarshalXmlStreamReader(streamReader);
307         }
308         else {
309             XMLEventReader eventReader = TraxUtils.getXMLEventReader(staxSource);
310             if (eventReader != null) {
311                 return unmarshalXmlEventReader(eventReader);
312             }
313             else {
314                 throw new IllegalArgumentException("StaxSource contains neither XMLStreamReader nor XMLEventReader");
315             }
316         }
317     }
318 
319     /**
320      * Template method for handling <code>SAXSource</code>s. This implementation defers to
321      * <code>unmarshalSaxReader</code>.
322      *
323      * @param saxSource the <code>SAXSource</code>
324      * @return the object graph
325      * @throws XmlMappingException if the given source cannot be mapped to an object
326      * @throws IOException         if an I/O Exception occurs
327      * @see #unmarshalSaxReader(org.xml.sax.XMLReader,org.xml.sax.InputSource)
328      */
329     protected Object unmarshalSaxSource(SAXSource saxSource) throws XmlMappingException, IOException {
330         if (saxSource.getXMLReader() == null) {
331             try {
332                 saxSource.setXMLReader(createXmlReader());
333             }
334             catch (SAXException ex) {
335                 throw new UnmarshallingFailureException("Could not create XMLReader for SAXSource: " + ex.getMessage(),
336                         ex);
337             }
338         }
339         if (saxSource.getInputSource() == null) {
340             saxSource.setInputSource(new InputSource());
341         }
342         return unmarshalSaxReader(saxSource.getXMLReader(), saxSource.getInputSource());
343     }
344 
345     /**
346      * Template method for handling <code>StreamSource</code>s. This implementation defers to
347      * <code>unmarshalInputStream</code>, or <code>unmarshalReader</code>.
348      *
349      * @param streamSource the <code>StreamSource</code>
350      * @return the object graph
351      * @throws IOException         if an I/O exception occurs
352      * @throws XmlMappingException if the given source cannot be mapped to an object
353      */
354     protected Object unmarshalStreamSource(StreamSource streamSource) throws XmlMappingException, IOException {
355         if (streamSource.getInputStream() != null) {
356             return unmarshalInputStream(streamSource.getInputStream());
357         }
358         else if (streamSource.getReader() != null) {
359             return unmarshalReader(streamSource.getReader());
360         }
361         else {
362             throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader");
363         }
364     }
365 
366     //
367     // Abstract template methods
368     //
369 
370     /**
371      * Abstract template method for marshalling the given object graph to a DOM <code>Node</code>.
372      * <p/>
373      * In practice, node is be a <code>Document</code> node, a <code>DocumentFragment</code> node, or a
374      * <code>Element</code> node. In other words, a node that accepts children.
375      *
376      * @param graph the root of the object graph to marshal
377      * @param node  The DOM node that will contain the result tree
378      * @throws XmlMappingException if the given object cannot be marshalled to the DOM node
379      * @see org.w3c.dom.Document
380      * @see org.w3c.dom.DocumentFragment
381      * @see org.w3c.dom.Element
382      */
383     protected abstract void marshalDomNode(Object graph, Node node) throws XmlMappingException;
384 
385     /**
386      * Abstract template method for marshalling the given object to a StAX <code>XMLEventWriter</code>.
387      *
388      * @param graph       the root of the object graph to marshal
389      * @param eventWriter the <code>XMLEventWriter</code> to write to
390      * @throws XmlMappingException if the given object cannot be marshalled to the DOM node
391      */
392     protected abstract void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException;
393 
394     /**
395      * Abstract template method for marshalling the given object to a StAX <code>XMLStreamWriter</code>.
396      *
397      * @param graph        the root of the object graph to marshal
398      * @param streamWriter the <code>XMLStreamWriter</code> to write to
399      * @throws XmlMappingException if the given object cannot be marshalled to the DOM node
400      */
401     protected abstract void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter)
402             throws XmlMappingException;
403 
404     /**
405      * Abstract template method for marshalling the given object graph to a <code>OutputStream</code>.
406      *
407      * @param graph        the root of the object graph to marshal
408      * @param outputStream the <code>OutputStream</code> to write to
409      * @throws XmlMappingException if the given object cannot be marshalled to the writer
410      * @throws IOException         if an I/O exception occurs
411      */
412     protected abstract void marshalOutputStream(Object graph, OutputStream outputStream)
413             throws XmlMappingException, IOException;
414 
415     /**
416      * Abstract template method for marshalling the given object graph to a SAX <code>ContentHandler</code>.
417      *
418      * @param graph          the root of the object graph to marshal
419      * @param contentHandler the SAX <code>ContentHandler</code>
420      * @param lexicalHandler the SAX2 <code>LexicalHandler</code>. Can be <code>null</code>.
421      * @throws XmlMappingException if the given object cannot be marshalled to the handlers
422      */
423     protected abstract void marshalSaxHandlers(Object graph,
424                                                ContentHandler contentHandler,
425                                                LexicalHandler lexicalHandler) throws XmlMappingException;
426 
427     /**
428      * Abstract template method for marshalling the given object graph to a <code>Writer</code>.
429      *
430      * @param graph  the root of the object graph to marshal
431      * @param writer the <code>Writer</code> to write to
432      * @throws XmlMappingException if the given object cannot be marshalled to the writer
433      * @throws IOException         if an I/O exception occurs
434      */
435     protected abstract void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException;
436 
437     /**
438      * Abstract template method for unmarshalling from a given DOM <code>Node</code>.
439      *
440      * @param node The DOM node that contains the objects to be unmarshalled
441      * @return the object graph
442      * @throws XmlMappingException if the given DOM node cannot be mapped to an object
443      */
444     protected abstract Object unmarshalDomNode(Node node) throws XmlMappingException;
445 
446     /**
447      * Abstract template method for unmarshalling from a given Stax <code>XMLEventReader</code>.
448      *
449      * @param eventReader The <code>XMLEventReader</code> to read from
450      * @return the object graph
451      * @throws XmlMappingException if the given event reader cannot be converted to an object
452      */
453     protected abstract Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException;
454 
455     /**
456      * Abstract template method for unmarshalling from a given Stax <code>XMLStreamReader</code>.
457      *
458      * @param streamReader The <code>XMLStreamReader</code> to read from
459      * @return the object graph
460      * @throws XmlMappingException if the given stream reader cannot be converted to an object
461      */
462     protected abstract Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException;
463 
464     /**
465      * Abstract template method for unmarshalling from a given <code>InputStream</code>.
466      *
467      * @param inputStream the <code>InputStreamStream</code> to read from
468      * @return the object graph
469      * @throws XmlMappingException if the given stream cannot be converted to an object
470      * @throws IOException         if an I/O exception occurs
471      */
472     protected abstract Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException;
473 
474     /**
475      * Abstract template method for unmarshalling from a given <code>Reader</code>.
476      *
477      * @param reader the <code>Reader</code> to read from
478      * @return the object graph
479      * @throws XmlMappingException if the given reader cannot be converted to an object
480      * @throws IOException         if an I/O exception occurs
481      */
482     protected abstract Object unmarshalReader(Reader reader) throws XmlMappingException, IOException;
483 
484     /**
485      * Abstract template method for unmarshalling using a given SAX <code>XMLReader</code> and
486      * <code>InputSource</code>.
487      *
488      * @param xmlReader   the SAX <code>XMLReader</code> to parse with
489      * @param inputSource the input source to parse from
490      * @return the object graph
491      * @throws XmlMappingException if the given reader and input source cannot be converted to an object
492      * @throws java.io.IOException if an I/O exception occurs
493      */
494     protected abstract Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
495             throws XmlMappingException, IOException;
496 }