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        protected XmlMappingException 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}