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