1 /* 2 * Copyright 2002-2009 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.xmlbeans; 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 java.io.FilterInputStream; 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.lang.ref.WeakReference; 28 import java.nio.CharBuffer; 29 import javax.xml.stream.XMLEventReader; 30 import javax.xml.stream.XMLEventWriter; 31 import javax.xml.stream.XMLStreamReader; 32 import javax.xml.stream.XMLStreamWriter; 33 34 import org.apache.xmlbeans.XMLStreamValidationException; 35 import org.apache.xmlbeans.XmlError; 36 import org.apache.xmlbeans.XmlException; 37 import org.apache.xmlbeans.XmlObject; 38 import org.apache.xmlbeans.XmlOptions; 39 import org.apache.xmlbeans.XmlSaxHandler; 40 import org.apache.xmlbeans.XmlValidationError; 41 import org.apache.commons.logging.Log; 42 import org.apache.commons.logging.LogFactory; 43 import org.w3c.dom.Document; 44 import org.w3c.dom.Node; 45 import org.w3c.dom.NodeList; 46 import org.xml.sax.ContentHandler; 47 import org.xml.sax.InputSource; 48 import org.xml.sax.SAXException; 49 import org.xml.sax.SAXNotRecognizedException; 50 import org.xml.sax.SAXNotSupportedException; 51 import org.xml.sax.XMLReader; 52 import org.xml.sax.ext.LexicalHandler; 53 54 import org.springframework.oxm.Marshaller; 55 import org.springframework.oxm.MarshallingFailureException; 56 import org.springframework.oxm.UncategorizedMappingException; 57 import org.springframework.oxm.UnmarshallingFailureException; 58 import org.springframework.oxm.ValidationFailureException; 59 import org.springframework.oxm.XmlMappingException; 60 import org.springframework.oxm.support.AbstractMarshaller; 61 import org.springframework.util.xml.StaxUtils; 62 63 /** 64 * Implementation of the {@link Marshaller} interface for Apache XMLBeans. 65 * 66 * <p>Options can be set by setting the <code>xmlOptions</code> property. 67 * The {@link XmlOptionsFactoryBean} is provided to easily set up an {@link XmlOptions} instance. 68 * 69 * <p>Unmarshalled objects can be validated by setting the <code>validating</code> property, 70 * or by calling the {@link #validate(XmlObject)} method directly. Invalid objects will 71 * result in an {@link ValidationFailureException}. 72 * 73 * <p><b>NOTE:</b> Due to the nature of XMLBeans, this marshaller requires 74 * all passed objects to be of type {@link XmlObject}. 75 * 76 * @author Arjen Poutsma 77 * @since 3.0 78 * @see #setValidating 79 * @see #setXmlOptions 80 * @see XmlOptionsFactoryBean 81 */ 82 public class XmlBeansMarshaller extends AbstractMarshaller { 83 84 private XmlOptions xmlOptions; 85 86 private boolean validating = false; 87 88 89 /** 90 * Set the <code>XmlOptions</code>. 91 * @see XmlOptionsFactoryBean 92 */ setXmlOptions(XmlOptions xmlOptions)93 public void setXmlOptions(XmlOptions xmlOptions) { 94 this.xmlOptions = xmlOptions; 95 } 96 97 /** 98 * Return the <code>XmlOptions</code>. 99 */ getXmlOptions()100 public XmlOptions getXmlOptions() { 101 return this.xmlOptions; 102 } 103 104 /** 105 * Set whether this marshaller should validate in- and outgoing documents. 106 * Default is <code>false</code>. 107 */ setValidating(boolean validating)108 public void setValidating(boolean validating) { 109 this.validating = validating; 110 } 111 112 /** 113 * Return whether this marshaller should validate in- and outgoing documents. 114 */ isValidating()115 public boolean isValidating() { 116 return this.validating; 117 } 118 119 120 /** 121 * This implementation returns true if the given class is an implementation of {@link XmlObject}. 122 */ supports(Class<?> clazz)123 public boolean supports(Class<?> clazz) { 124 return XmlObject.class.isAssignableFrom(clazz); 125 } 126 127 @Override marshalDomNode(Object graph, Node node)128 protected final void marshalDomNode(Object graph, Node node) throws XmlMappingException { 129 Document document = node.getNodeType() == Node.DOCUMENT_NODE ? (Document) node : node.getOwnerDocument(); 130 Node xmlBeansNode = ((XmlObject) graph).newDomNode(getXmlOptions()); 131 NodeList xmlBeansChildNodes = xmlBeansNode.getChildNodes(); 132 for (int i = 0; i < xmlBeansChildNodes.getLength(); i++) { 133 Node xmlBeansChildNode = xmlBeansChildNodes.item(i); 134 Node importedNode = document.importNode(xmlBeansChildNode, true); 135 node.appendChild(importedNode); 136 } 137 } 138 139 @Override marshalOutputStream(Object graph, OutputStream outputStream)140 protected final void marshalOutputStream(Object graph, OutputStream outputStream) 141 throws XmlMappingException, IOException { 142 143 ((XmlObject) graph).save(outputStream, getXmlOptions()); 144 } 145 146 @Override marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler)147 protected final void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) 148 throws XmlMappingException { 149 try { 150 ((XmlObject) graph).save(contentHandler, lexicalHandler, getXmlOptions()); 151 } 152 catch (SAXException ex) { 153 throw convertXmlBeansException(ex, true); 154 } 155 } 156 157 @Override marshalWriter(Object graph, Writer writer)158 protected final void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { 159 ((XmlObject) graph).save(writer, getXmlOptions()); 160 } 161 162 @Override marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter)163 protected final void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) { 164 ContentHandler contentHandler = StaxUtils.createContentHandler(eventWriter); 165 marshalSaxHandlers(graph, contentHandler, null); 166 } 167 168 @Override marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter)169 protected final void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { 170 ContentHandler contentHandler = StaxUtils.createContentHandler(streamWriter); 171 marshalSaxHandlers(graph, contentHandler, null); 172 } 173 174 @Override unmarshalDomNode(Node node)175 protected final Object unmarshalDomNode(Node node) throws XmlMappingException { 176 try { 177 XmlObject object = XmlObject.Factory.parse(node, getXmlOptions()); 178 validate(object); 179 return object; 180 } 181 catch (XmlException ex) { 182 throw convertXmlBeansException(ex, false); 183 } 184 } 185 186 @Override unmarshalInputStream(InputStream inputStream)187 protected final Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { 188 try { 189 InputStream nonClosingInputStream = new NonClosingInputStream(inputStream); 190 XmlObject object = XmlObject.Factory.parse(nonClosingInputStream, getXmlOptions()); 191 validate(object); 192 return object; 193 } 194 catch (XmlException ex) { 195 throw convertXmlBeansException(ex, false); 196 } 197 } 198 199 @Override unmarshalReader(Reader reader)200 protected final Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { 201 try { 202 Reader nonClosingReader = new NonClosingReader(reader); 203 XmlObject object = XmlObject.Factory.parse(nonClosingReader, getXmlOptions()); 204 validate(object); 205 return object; 206 } 207 catch (XmlException ex) { 208 throw convertXmlBeansException(ex, false); 209 } 210 } 211 212 @Override unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)213 protected final Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) 214 throws XmlMappingException, IOException { 215 XmlSaxHandler saxHandler = XmlObject.Factory.newXmlSaxHandler(getXmlOptions()); 216 xmlReader.setContentHandler(saxHandler.getContentHandler()); 217 try { 218 xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", saxHandler.getLexicalHandler()); 219 } 220 catch (SAXNotRecognizedException e) { 221 // ignore 222 } 223 catch (SAXNotSupportedException e) { 224 // ignore 225 } 226 try { 227 xmlReader.parse(inputSource); 228 XmlObject object = saxHandler.getObject(); 229 validate(object); 230 return object; 231 } 232 catch (SAXException ex) { 233 throw convertXmlBeansException(ex, false); 234 } 235 catch (XmlException ex) { 236 throw convertXmlBeansException(ex, false); 237 } 238 } 239 240 @Override unmarshalXmlEventReader(XMLEventReader eventReader)241 protected final Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException { 242 XMLReader reader = StaxUtils.createXMLReader(eventReader); 243 try { 244 return unmarshalSaxReader(reader, new InputSource()); 245 } 246 catch (IOException ex) { 247 throw convertXmlBeansException(ex, false); 248 } 249 } 250 251 @Override unmarshalXmlStreamReader(XMLStreamReader streamReader)252 protected final Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException { 253 try { 254 XmlObject object = XmlObject.Factory.parse(streamReader, getXmlOptions()); 255 validate(object); 256 return object; 257 } 258 catch (XmlException ex) { 259 throw convertXmlBeansException(ex, false); 260 } 261 } 262 263 264 /** 265 * Validate the given <code>XmlObject</code>. 266 * @param object the xml object to validate 267 * @throws ValidationFailureException if the given object is not valid 268 */ validate(XmlObject object)269 protected void validate(XmlObject object) throws ValidationFailureException { 270 if (isValidating() && object != null) { 271 // create a temporary xmlOptions just for validation 272 XmlOptions validateOptions = getXmlOptions() != null ? getXmlOptions() : new XmlOptions(); 273 List errorsList = new ArrayList(); 274 validateOptions.setErrorListener(errorsList); 275 if (!object.validate(validateOptions)) { 276 StringBuilder builder = new StringBuilder("Could not validate XmlObject :"); 277 for (Object anErrorsList : errorsList) { 278 XmlError xmlError = (XmlError) anErrorsList; 279 if (xmlError instanceof XmlValidationError) { 280 builder.append(xmlError.toString()); 281 } 282 } 283 throw new ValidationFailureException("XMLBeans validation failure", 284 new XmlException(builder.toString(), null, errorsList)); 285 } 286 } 287 } 288 289 /** 290 * Convert the given XMLBeans exception to an appropriate exception from the 291 * <code>org.springframework.oxm</code> hierarchy. 292 * <p>A boolean flag is used to indicate whether this exception occurs during marshalling or 293 * unmarshalling, since XMLBeans itself does not make this distinction in its exception hierarchy. 294 * @param ex XMLBeans Exception that occured 295 * @param marshalling indicates whether the exception occurs during marshalling (<code>true</code>), 296 * or unmarshalling (<code>false</code>) 297 * @return the corresponding <code>XmlMappingException</code> 298 */ convertXmlBeansException(Exception ex, boolean marshalling)299 protected XmlMappingException convertXmlBeansException(Exception ex, boolean marshalling) { 300 if (ex instanceof XMLStreamValidationException) { 301 return new ValidationFailureException("XmlBeans validation exception", ex); 302 } 303 else if (ex instanceof XmlException || ex instanceof SAXException) { 304 if (marshalling) { 305 return new MarshallingFailureException("XMLBeans marshalling exception", ex); 306 } 307 else { 308 return new UnmarshallingFailureException("XMLBeans unmarshalling exception", ex); 309 } 310 } 311 else { 312 // fallback 313 return new UncategorizedMappingException("Unknown XMLBeans exception", ex); 314 } 315 } 316 317 /** 318 * See SPR-7034 319 */ 320 private static class NonClosingInputStream extends InputStream { 321 322 private final WeakReference<InputStream> in; 323 NonClosingInputStream(InputStream in)324 private NonClosingInputStream(InputStream in) { 325 this.in = new WeakReference<InputStream>(in); 326 } 327 getInputStream()328 private InputStream getInputStream() { 329 return this.in.get(); 330 } 331 332 @Override read()333 public int read() throws IOException { 334 InputStream in = getInputStream(); 335 return in != null ? in.read() : -1; 336 } 337 338 @Override read(byte[] b)339 public int read(byte[] b) throws IOException { 340 InputStream in = getInputStream(); 341 return in != null ? in.read(b) : -1; 342 } 343 344 @Override read(byte[] b, int off, int len)345 public int read(byte[] b, int off, int len) throws IOException { 346 InputStream in = getInputStream(); 347 return in != null ? in.read(b, off, len) : -1; 348 } 349 350 @Override skip(long n)351 public long skip(long n) throws IOException { 352 InputStream in = getInputStream(); 353 return in != null ? in.skip(n) : 0; 354 } 355 356 @Override markSupported()357 public boolean markSupported() { 358 InputStream in = getInputStream(); 359 return in != null && in.markSupported(); 360 } 361 362 @Override mark(int readlimit)363 public void mark(int readlimit) { 364 InputStream in = getInputStream(); 365 if (in != null) { 366 in.mark(readlimit); 367 } 368 } 369 370 @Override reset()371 public void reset() throws IOException { 372 InputStream in = getInputStream(); 373 if (in != null) { 374 in.reset(); 375 } 376 } 377 378 @Override available()379 public int available() throws IOException { 380 InputStream in = getInputStream(); 381 return in != null ? in.available() : 0; 382 } 383 384 @Override close()385 public void close() throws IOException { 386 InputStream in = getInputStream(); 387 if(in != null) { 388 this.in.clear(); 389 } 390 } 391 } 392 393 private static class NonClosingReader extends Reader { 394 395 private final WeakReference<Reader> reader; 396 NonClosingReader(Reader reader)397 private NonClosingReader(Reader reader) { 398 this.reader = new WeakReference<Reader>(reader); 399 } 400 getReader()401 private Reader getReader() { 402 return this.reader.get(); 403 } 404 405 @Override read(CharBuffer target)406 public int read(CharBuffer target) throws IOException { 407 Reader rdr = getReader(); 408 return rdr != null ? rdr.read(target) : -1; 409 } 410 411 @Override read()412 public int read() throws IOException { 413 Reader rdr = getReader(); 414 return rdr != null ? rdr.read() : -1; 415 } 416 417 @Override read(char[] cbuf)418 public int read(char[] cbuf) throws IOException { 419 Reader rdr = getReader(); 420 return rdr != null ? rdr.read(cbuf) : -1; 421 } 422 423 @Override read(char[] cbuf, int off, int len)424 public int read(char[] cbuf, int off, int len) throws IOException { 425 Reader rdr = getReader(); 426 return rdr != null ? rdr.read(cbuf, off, len) : -1; 427 } 428 429 @Override skip(long n)430 public long skip(long n) throws IOException { 431 Reader rdr = getReader(); 432 return rdr != null ? rdr.skip(n) : 0; 433 } 434 435 @Override ready()436 public boolean ready() throws IOException { 437 Reader rdr = getReader(); 438 return rdr != null && rdr.ready(); 439 } 440 441 @Override markSupported()442 public boolean markSupported() { 443 Reader rdr = getReader(); 444 return rdr != null && rdr.markSupported(); 445 } 446 447 @Override mark(int readAheadLimit)448 public void mark(int readAheadLimit) throws IOException { 449 Reader rdr = getReader(); 450 if (rdr != null) { 451 rdr.mark(readAheadLimit); 452 } 453 } 454 455 @Override reset()456 public void reset() throws IOException { 457 Reader rdr = getReader(); 458 if (rdr != null) { 459 rdr.reset(); 460 } 461 } 462 463 @Override close()464 public void close() throws IOException { 465 Reader rdr = getReader(); 466 if (rdr != null) { 467 this.reader.clear(); 468 } 469 } 470 471 } 472 473 } 474