001/* 002 * Copyright 2002-2014 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.oxm.xmlbeans; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.io.Reader; 023import java.io.Writer; 024import java.lang.ref.WeakReference; 025import java.nio.CharBuffer; 026import java.util.ArrayList; 027import java.util.List; 028import javax.xml.stream.XMLEventReader; 029import javax.xml.stream.XMLEventWriter; 030import javax.xml.stream.XMLStreamReader; 031import javax.xml.stream.XMLStreamWriter; 032 033import org.apache.xmlbeans.XMLStreamValidationException; 034import org.apache.xmlbeans.XmlError; 035import org.apache.xmlbeans.XmlException; 036import org.apache.xmlbeans.XmlObject; 037import org.apache.xmlbeans.XmlOptions; 038import org.apache.xmlbeans.XmlSaxHandler; 039import org.apache.xmlbeans.XmlValidationError; 040import org.w3c.dom.Document; 041import org.w3c.dom.Node; 042import org.w3c.dom.NodeList; 043import org.xml.sax.ContentHandler; 044import org.xml.sax.InputSource; 045import org.xml.sax.SAXException; 046import org.xml.sax.SAXNotRecognizedException; 047import org.xml.sax.SAXNotSupportedException; 048import org.xml.sax.XMLReader; 049import org.xml.sax.ext.LexicalHandler; 050 051import org.springframework.oxm.Marshaller; 052import org.springframework.oxm.MarshallingFailureException; 053import org.springframework.oxm.UncategorizedMappingException; 054import org.springframework.oxm.UnmarshallingFailureException; 055import org.springframework.oxm.ValidationFailureException; 056import org.springframework.oxm.XmlMappingException; 057import org.springframework.oxm.support.AbstractMarshaller; 058import org.springframework.util.xml.StaxUtils; 059 060/** 061 * Implementation of the {@link Marshaller} interface for Apache XMLBeans. 062 * 063 * <p>Options can be set by setting the {@code xmlOptions} property. 064 * The {@link XmlOptionsFactoryBean} is provided to easily set up an {@link XmlOptions} instance. 065 * 066 * <p>Unmarshalled objects can be validated by setting the {@code validating} property, 067 * or by calling the {@link #validate(XmlObject)} method directly. Invalid objects will 068 * result in an {@link ValidationFailureException}. 069 * 070 * <p><b>NOTE:</b> Due to the nature of XMLBeans, this marshaller requires 071 * all passed objects to be of type {@link XmlObject}. 072 * 073 * @author Arjen Poutsma 074 * @since 3.0 075 * @see #setValidating 076 * @see #setXmlOptions 077 * @see XmlOptionsFactoryBean 078 * @deprecated as of Spring 4.2, following the XMLBeans retirement at Apache 079 */ 080@Deprecated 081public class XmlBeansMarshaller extends AbstractMarshaller { 082 083 private XmlOptions xmlOptions; 084 085 private boolean validating = false; 086 087 088 /** 089 * Set the {@code XmlOptions}. 090 * @see XmlOptionsFactoryBean 091 */ 092 public void setXmlOptions(XmlOptions xmlOptions) { 093 this.xmlOptions = xmlOptions; 094 } 095 096 /** 097 * Return the {@code XmlOptions}. 098 */ 099 public XmlOptions getXmlOptions() { 100 return this.xmlOptions; 101 } 102 103 /** 104 * Set whether this marshaller should validate in- and outgoing documents. 105 * Default is {@code false}. 106 */ 107 public void setValidating(boolean validating) { 108 this.validating = validating; 109 } 110 111 /** 112 * Return whether this marshaller should validate in- and outgoing documents. 113 */ 114 public boolean isValidating() { 115 return this.validating; 116 } 117 118 119 /** 120 * This implementation returns true if the given class is an implementation of {@link XmlObject}. 121 */ 122 @Override 123 public boolean supports(Class<?> clazz) { 124 return XmlObject.class.isAssignableFrom(clazz); 125 } 126 127 128 @Override 129 protected void marshalDomNode(Object graph, Node node) throws XmlMappingException { 130 Document document = (node.getNodeType() == Node.DOCUMENT_NODE ? (Document) node : node.getOwnerDocument()); 131 Node xmlBeansNode = ((XmlObject) graph).newDomNode(getXmlOptions()); 132 NodeList xmlBeansChildNodes = xmlBeansNode.getChildNodes(); 133 for (int i = 0; i < xmlBeansChildNodes.getLength(); i++) { 134 Node xmlBeansChildNode = xmlBeansChildNodes.item(i); 135 Node importedNode = document.importNode(xmlBeansChildNode, true); 136 node.appendChild(importedNode); 137 } 138 } 139 140 @Override 141 protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) { 142 ContentHandler contentHandler = StaxUtils.createContentHandler(eventWriter); 143 LexicalHandler lexicalHandler = null; 144 if (contentHandler instanceof LexicalHandler) { 145 lexicalHandler = (LexicalHandler) contentHandler; 146 } 147 marshalSaxHandlers(graph, contentHandler, lexicalHandler); 148 } 149 150 @Override 151 protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { 152 ContentHandler contentHandler = StaxUtils.createContentHandler(streamWriter); 153 LexicalHandler lexicalHandler = null; 154 if (contentHandler instanceof LexicalHandler) { 155 lexicalHandler = (LexicalHandler) contentHandler; 156 } 157 marshalSaxHandlers(graph, contentHandler, lexicalHandler); 158 } 159 160 @Override 161 protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) 162 throws XmlMappingException { 163 try { 164 ((XmlObject) graph).save(contentHandler, lexicalHandler, getXmlOptions()); 165 } 166 catch (SAXException ex) { 167 throw convertXmlBeansException(ex, true); 168 } 169 } 170 171 @Override 172 protected void marshalOutputStream(Object graph, OutputStream outputStream) 173 throws XmlMappingException, IOException { 174 175 ((XmlObject) graph).save(outputStream, getXmlOptions()); 176 } 177 178 @Override 179 protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { 180 ((XmlObject) graph).save(writer, getXmlOptions()); 181 } 182 183 184 @Override 185 protected Object unmarshalDomNode(Node node) throws XmlMappingException { 186 try { 187 XmlObject object = XmlObject.Factory.parse(node, getXmlOptions()); 188 validate(object); 189 return object; 190 } 191 catch (XmlException ex) { 192 throw convertXmlBeansException(ex, false); 193 } 194 } 195 196 @Override 197 protected Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException { 198 XMLReader reader = StaxUtils.createXMLReader(eventReader); 199 try { 200 return unmarshalSaxReader(reader, new InputSource()); 201 } 202 catch (IOException ex) { 203 throw convertXmlBeansException(ex, false); 204 } 205 } 206 207 @Override 208 protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException { 209 try { 210 XmlObject object = XmlObject.Factory.parse(streamReader, getXmlOptions()); 211 validate(object); 212 return object; 213 } 214 catch (XmlException ex) { 215 throw convertXmlBeansException(ex, false); 216 } 217 } 218 219 @Override 220 protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) 221 throws XmlMappingException, IOException { 222 223 XmlSaxHandler saxHandler = XmlObject.Factory.newXmlSaxHandler(getXmlOptions()); 224 xmlReader.setContentHandler(saxHandler.getContentHandler()); 225 try { 226 xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", saxHandler.getLexicalHandler()); 227 } 228 catch (SAXNotRecognizedException ex) { 229 // ignore 230 } 231 catch (SAXNotSupportedException ex) { 232 // ignore 233 } 234 try { 235 xmlReader.parse(inputSource); 236 XmlObject object = saxHandler.getObject(); 237 validate(object); 238 return object; 239 } 240 catch (SAXException ex) { 241 throw convertXmlBeansException(ex, false); 242 } 243 catch (XmlException ex) { 244 throw convertXmlBeansException(ex, false); 245 } 246 } 247 248 @Override 249 protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { 250 try { 251 InputStream nonClosingInputStream = new NonClosingInputStream(inputStream); 252 XmlObject object = XmlObject.Factory.parse(nonClosingInputStream, getXmlOptions()); 253 validate(object); 254 return object; 255 } 256 catch (XmlException ex) { 257 throw convertXmlBeansException(ex, false); 258 } 259 } 260 261 @Override 262 protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { 263 try { 264 Reader nonClosingReader = new NonClosingReader(reader); 265 XmlObject object = XmlObject.Factory.parse(nonClosingReader, getXmlOptions()); 266 validate(object); 267 return object; 268 } 269 catch (XmlException ex) { 270 throw convertXmlBeansException(ex, false); 271 } 272 } 273 274 275 /** 276 * Validate the given {@code XmlObject}. 277 * @param object the xml object to validate 278 * @throws ValidationFailureException if the given object is not valid 279 */ 280 protected void validate(XmlObject object) throws ValidationFailureException { 281 if (isValidating() && object != null) { 282 XmlOptions validateOptions = getXmlOptions(); 283 if (validateOptions == null) { 284 // Create temporary XmlOptions just for validation 285 validateOptions = new XmlOptions(); 286 } 287 List<XmlError> errorsList = new ArrayList<XmlError>(); 288 validateOptions.setErrorListener(errorsList); 289 if (!object.validate(validateOptions)) { 290 StringBuilder sb = new StringBuilder("Failed to validate XmlObject: "); 291 boolean first = true; 292 for (XmlError error : errorsList) { 293 if (error instanceof XmlValidationError) { 294 if (!first) { 295 sb.append("; "); 296 } 297 sb.append(error.toString()); 298 first = false; 299 } 300 } 301 throw new ValidationFailureException("XMLBeans validation failure", 302 new XmlException(sb.toString(), null, errorsList)); 303 } 304 } 305 } 306 307 /** 308 * Convert the given XMLBeans exception to an appropriate exception from the 309 * {@code org.springframework.oxm} hierarchy. 310 * <p>A boolean flag is used to indicate whether this exception occurs during marshalling or 311 * unmarshalling, since XMLBeans itself does not make this distinction in its exception hierarchy. 312 * @param ex XMLBeans Exception that occured 313 * @param marshalling indicates whether the exception occurs during marshalling ({@code true}), 314 * or unmarshalling ({@code false}) 315 * @return the corresponding {@code XmlMappingException} 316 */ 317 n convertXmlBeansException(Exception ex, boolean marshalling) { 318 if (ex instanceof XMLStreamValidationException) { 319 return new ValidationFailureException("XMLBeans validation exception", ex); 320 } 321 else if (ex instanceof XmlException || ex instanceof SAXException) { 322 if (marshalling) { 323 return new MarshallingFailureException("XMLBeans marshalling exception", ex); 324 } 325 else { 326 return new UnmarshallingFailureException("XMLBeans unmarshalling exception", ex); 327 } 328 } 329 else { 330 // fallback 331 return new UncategorizedMappingException("Unknown XMLBeans exception", ex); 332 } 333 } 334 335 336 private static class NonClosingInputStream extends InputStream { 337 338 private final WeakReference<InputStream> in; 339 340 public NonClosingInputStream(InputStream in) { 341 this.in = new WeakReference<InputStream>(in); 342 } 343 344 private InputStream getInputStream() { 345 return this.in.get(); 346 } 347 348 @Override 349 public int read() throws IOException { 350 InputStream in = getInputStream(); 351 return (in != null ? in.read() : -1); 352 } 353 354 @Override 355 public int read(byte[] b) throws IOException { 356 InputStream in = getInputStream(); 357 return (in != null ? in.read(b) : -1); 358 } 359 360 @Override 361 public int read(byte[] b, int off, int len) throws IOException { 362 InputStream in = getInputStream(); 363 return (in != null ? in.read(b, off, len) : -1); 364 } 365 366 @Override 367 public long skip(long n) throws IOException { 368 InputStream in = getInputStream(); 369 return (in != null ? in.skip(n) : 0); 370 } 371 372 @Override 373 public boolean markSupported() { 374 InputStream in = getInputStream(); 375 return (in != null && in.markSupported()); 376 } 377 378 @Override 379 public void mark(int readlimit) { 380 InputStream in = getInputStream(); 381 if (in != null) { 382 in.mark(readlimit); 383 } 384 } 385 386 @Override 387 public void reset() throws IOException { 388 InputStream in = getInputStream(); 389 if (in != null) { 390 in.reset(); 391 } 392 } 393 394 @Override 395 public int available() throws IOException { 396 InputStream in = getInputStream(); 397 return (in != null ? in.available() : 0); 398 } 399 400 @Override 401 public void close() throws IOException { 402 InputStream in = getInputStream(); 403 if (in != null) { 404 this.in.clear(); 405 } 406 } 407 } 408 409 410 private static class NonClosingReader extends Reader { 411 412 private final WeakReference<Reader> reader; 413 414 public NonClosingReader(Reader reader) { 415 this.reader = new WeakReference<Reader>(reader); 416 } 417 418 private Reader getReader() { 419 return this.reader.get(); 420 } 421 422 @Override 423 public int read(CharBuffer target) throws IOException { 424 Reader rdr = getReader(); 425 return (rdr != null ? rdr.read(target) : -1); 426 } 427 428 @Override 429 public int read() throws IOException { 430 Reader rdr = getReader(); 431 return (rdr != null ? rdr.read() : -1); 432 } 433 434 @Override 435 public int read(char[] cbuf) throws IOException { 436 Reader rdr = getReader(); 437 return (rdr != null ? rdr.read(cbuf) : -1); 438 } 439 440 @Override 441 public int read(char[] cbuf, int off, int len) throws IOException { 442 Reader rdr = getReader(); 443 return (rdr != null ? rdr.read(cbuf, off, len) : -1); 444 } 445 446 @Override 447 public long skip(long n) throws IOException { 448 Reader rdr = getReader(); 449 return (rdr != null ? rdr.skip(n) : 0); 450 } 451 452 @Override 453 public boolean ready() throws IOException { 454 Reader rdr = getReader(); 455 return (rdr != null && rdr.ready()); 456 } 457 458 @Override 459 public boolean markSupported() { 460 Reader rdr = getReader(); 461 return (rdr != null && rdr.markSupported()); 462 } 463 464 @Override 465 public void mark(int readAheadLimit) throws IOException { 466 Reader rdr = getReader(); 467 if (rdr != null) { 468 rdr.mark(readAheadLimit); 469 } 470 } 471 472 @Override 473 public void reset() throws IOException { 474 Reader rdr = getReader(); 475 if (rdr != null) { 476 rdr.reset(); 477 } 478 } 479 480 @Override 481 public void close() throws IOException { 482 Reader rdr = getReader(); 483 if (rdr != null) { 484 this.reader.clear(); 485 } 486 } 487 } 488 489}