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 }