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