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 }