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