001/*
002 * Copyright 2002-2019 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.jibx;
018
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.io.Reader;
025import java.io.Writer;
026
027import javax.xml.stream.XMLEventReader;
028import javax.xml.stream.XMLEventWriter;
029import javax.xml.stream.XMLStreamException;
030import javax.xml.stream.XMLStreamReader;
031import javax.xml.stream.XMLStreamWriter;
032import javax.xml.transform.OutputKeys;
033import javax.xml.transform.Result;
034import javax.xml.transform.Source;
035import javax.xml.transform.Transformer;
036import javax.xml.transform.TransformerException;
037import javax.xml.transform.TransformerFactory;
038import javax.xml.transform.dom.DOMResult;
039import javax.xml.transform.dom.DOMSource;
040import javax.xml.transform.sax.SAXResult;
041import javax.xml.transform.sax.SAXSource;
042import javax.xml.transform.stream.StreamResult;
043import javax.xml.transform.stream.StreamSource;
044
045import org.jibx.runtime.BindingDirectory;
046import org.jibx.runtime.IBindingFactory;
047import org.jibx.runtime.IMarshallingContext;
048import org.jibx.runtime.IUnmarshallingContext;
049import org.jibx.runtime.IXMLReader;
050import org.jibx.runtime.IXMLWriter;
051import org.jibx.runtime.JiBXException;
052import org.jibx.runtime.ValidationException;
053import org.jibx.runtime.impl.MarshallingContext;
054import org.jibx.runtime.impl.StAXReaderWrapper;
055import org.jibx.runtime.impl.StAXWriter;
056import org.jibx.runtime.impl.UnmarshallingContext;
057import org.w3c.dom.Node;
058import org.xml.sax.ContentHandler;
059import org.xml.sax.InputSource;
060import org.xml.sax.XMLReader;
061import org.xml.sax.ext.LexicalHandler;
062
063import org.springframework.beans.factory.InitializingBean;
064import org.springframework.lang.Nullable;
065import org.springframework.oxm.MarshallingFailureException;
066import org.springframework.oxm.UnmarshallingFailureException;
067import org.springframework.oxm.ValidationFailureException;
068import org.springframework.oxm.XmlMappingException;
069import org.springframework.oxm.support.AbstractMarshaller;
070import org.springframework.util.Assert;
071import org.springframework.util.ClassUtils;
072import org.springframework.util.StringUtils;
073import org.springframework.util.xml.StaxUtils;
074
075/**
076 * Implementation of the {@code Marshaller} and {@code Unmarshaller} interfaces for JiBX.
077 *
078 * <p>The typical usage will be to set the {@code targetClass} and optionally the
079 * {@code bindingName} property on this bean.
080 *
081 * @author Arjen Poutsma
082 * @since 3.0
083 * @see org.jibx.runtime.IMarshallingContext
084 * @see org.jibx.runtime.IUnmarshallingContext
085 * @deprecated as of Spring Framework 5.1.5, due to the lack of activity on the JiBX project
086 */
087@Deprecated
088public class JibxMarshaller extends AbstractMarshaller implements InitializingBean {
089
090        private static final String DEFAULT_BINDING_NAME = "binding";
091
092
093        @Nullable
094        private Class<?> targetClass;
095
096        @Nullable
097        private String targetPackage;
098
099        @Nullable
100        private String bindingName;
101
102        private int indent = -1;
103
104        private String encoding = "UTF-8";
105
106        @Nullable
107        private Boolean standalone;
108
109        @Nullable
110        private String docTypeRootElementName;
111
112        @Nullable
113        private String docTypeSystemId;
114
115        @Nullable
116        private String docTypePublicId;
117
118        @Nullable
119        private String docTypeInternalSubset;
120
121        @Nullable
122        private IBindingFactory bindingFactory;
123
124        private final TransformerFactory transformerFactory = TransformerFactory.newInstance();
125
126
127        /**
128         * Set the target class for this instance. Setting either this property or the
129         * {@link #setTargetPackage(String) targetPackage} property is required.
130         * <p>If this property is set, {@link #setTargetPackage(String) targetPackage} is ignored.
131         */
132        public void setTargetClass(Class<?> targetClass) {
133                this.targetClass = targetClass;
134        }
135
136        /**
137         * Set the target package for this instance. Setting either this property or the
138         * {@link #setTargetClass(Class) targetClass} property is required.
139         * <p>If {@link #setTargetClass(Class) targetClass} is set, this property is ignored.
140         */
141        public void setTargetPackage(String targetPackage) {
142                this.targetPackage = targetPackage;
143        }
144
145        /**
146         * Set the optional binding name for this instance.
147         */
148        public void setBindingName(String bindingName) {
149                this.bindingName = bindingName;
150        }
151
152        /**
153         * Set the number of nesting indent spaces. Default is {@code -1}, i.e. no indentation.
154         */
155        public void setIndent(int indent) {
156                this.indent = indent;
157        }
158
159        /**
160         * Set the document encoding using for marshalling. Default is UTF-8.
161         */
162        public void setEncoding(String encoding) {
163                this.encoding = encoding;
164        }
165
166        @Override
167        protected String getDefaultEncoding() {
168                return this.encoding;
169        }
170
171        /**
172         * Set the document standalone flag for marshalling. By default, this flag is not present.
173         */
174        public void setStandalone(Boolean standalone) {
175                this.standalone = standalone;
176        }
177
178        /**
179         * Set the root element name for the DTD declaration written when marshalling.
180         * By default, this is {@code null} (i.e. no DTD declaration is written).
181         * <p>If set to a value, the system ID or public ID also need to be set.
182         * @see #setDocTypeSystemId(String)
183         * @see #setDocTypePublicId(String)
184         */
185        public void setDocTypeRootElementName(String docTypeRootElementName) {
186                this.docTypeRootElementName = docTypeRootElementName;
187        }
188
189        /**
190         * Set the system id for the DTD declaration written when marshalling.
191         * By default, this is {@code null}. Only used when the root element also has been set.
192         * <p>Set either this property or {@code docTypePublicId}, not both.
193         * @see #setDocTypeRootElementName(String)
194         */
195        public void setDocTypeSystemId(String docTypeSystemId) {
196                this.docTypeSystemId = docTypeSystemId;
197        }
198
199        /**
200         * Set the public id for the DTD declaration written when marshalling.
201         * By default, this is {@code null}. Only used when the root element also has been set.
202         * <p>Set either this property or {@code docTypeSystemId}, not both.
203         * @see #setDocTypeRootElementName(String)
204         */
205        public void setDocTypePublicId(String docTypePublicId) {
206                this.docTypePublicId = docTypePublicId;
207        }
208
209        /**
210         * Set the internal subset Id for the DTD declaration written when marshalling.
211         * By default, this is {@code null}. Only used when the root element also has been set.
212         * @see #setDocTypeRootElementName(String)
213         */
214        public void setDocTypeInternalSubset(String docTypeInternalSubset) {
215                this.docTypeInternalSubset = docTypeInternalSubset;
216        }
217
218
219        @Override
220        public void afterPropertiesSet() throws JiBXException {
221                if (this.targetClass != null) {
222                        if (StringUtils.hasLength(this.bindingName)) {
223                                if (logger.isDebugEnabled()) {
224                                        logger.debug("Configured for target class [" + this.targetClass +
225                                                        "] using binding [" + this.bindingName + "]");
226                                }
227                                this.bindingFactory = BindingDirectory.getFactory(this.bindingName, this.targetClass);
228                        }
229                        else {
230                                if (logger.isDebugEnabled()) {
231                                        logger.debug("Configured for target class [" + this.targetClass + "]");
232                                }
233                                this.bindingFactory = BindingDirectory.getFactory(this.targetClass);
234                        }
235                }
236                else if (this.targetPackage != null) {
237                        if (!StringUtils.hasLength(this.bindingName)) {
238                                this.bindingName = DEFAULT_BINDING_NAME;
239                        }
240                        if (logger.isDebugEnabled()) {
241                                logger.debug("Configured for target package [" + this.targetPackage +
242                                                "] using binding [" + this.bindingName + "]");
243                        }
244                        this.bindingFactory = BindingDirectory.getFactory(this.bindingName, this.targetPackage);
245                }
246                else {
247                        throw new IllegalArgumentException("Either 'targetClass' or 'targetPackage' is required");
248                }
249        }
250
251
252        @Override
253        public boolean supports(Class<?> clazz) {
254                Assert.notNull(clazz, "Class must not be null");
255                if (this.targetClass != null) {
256                        return (this.targetClass == clazz);
257                }
258                Assert.state(this.bindingFactory != null, "JibxMarshaller not initialized");
259                String[] mappedClasses = this.bindingFactory.getMappedClasses();
260                String className = clazz.getName();
261                for (String mappedClass : mappedClasses) {
262                        if (className.equals(mappedClass)) {
263                                return true;
264                        }
265                }
266                return false;
267        }
268
269
270        // Supported marshalling
271
272        @Override
273        protected void marshalOutputStream(Object graph, OutputStream outputStream)
274                        throws XmlMappingException, IOException {
275                try {
276                        IMarshallingContext marshallingContext = createMarshallingContext();
277                        marshallingContext.startDocument(this.encoding, this.standalone, outputStream);
278                        marshalDocument(marshallingContext, graph);
279                }
280                catch (JiBXException ex) {
281                        throw convertJibxException(ex, true);
282                }
283        }
284
285        @Override
286        protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException {
287                try {
288                        IMarshallingContext marshallingContext = createMarshallingContext();
289                        marshallingContext.startDocument(this.encoding, this.standalone, writer);
290                        marshalDocument(marshallingContext, graph);
291                }
292                catch (JiBXException ex) {
293                        throw convertJibxException(ex, true);
294                }
295        }
296
297        private void marshalDocument(IMarshallingContext marshallingContext, Object graph) throws IOException, JiBXException {
298                if (StringUtils.hasLength(this.docTypeRootElementName)) {
299                        IXMLWriter xmlWriter = marshallingContext.getXmlWriter();
300                        xmlWriter.writeDocType(this.docTypeRootElementName, this.docTypeSystemId,
301                                        this.docTypePublicId, this.docTypeInternalSubset);
302                }
303                marshallingContext.marshalDocument(graph);
304        }
305
306
307        // Unsupported marshalling
308
309        @Override
310        protected void marshalDomNode(Object graph, Node node) throws XmlMappingException {
311                try {
312                        // JiBX does not support DOM natively, so we write to a buffer first, and transform that to the Node
313                        Result result = new DOMResult(node);
314                        transformAndMarshal(graph, result);
315                }
316                catch (IOException ex) {
317                        throw new MarshallingFailureException("JiBX marshalling exception", ex);
318                }
319        }
320
321        @Override
322        protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) {
323                XMLStreamWriter streamWriter = StaxUtils.createEventStreamWriter(eventWriter);
324                marshalXmlStreamWriter(graph, streamWriter);
325        }
326
327        @Override
328        protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException {
329                try {
330                        MarshallingContext marshallingContext = (MarshallingContext) createMarshallingContext();
331                        IXMLWriter xmlWriter = new StAXWriter(marshallingContext.getNamespaces(), streamWriter);
332                        marshallingContext.setXmlWriter(xmlWriter);
333                        marshallingContext.marshalDocument(graph);
334                }
335                catch (JiBXException ex) {
336                        throw convertJibxException(ex, false);
337                }
338        }
339
340        @Override
341        protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, @Nullable LexicalHandler lexicalHandler)
342                        throws XmlMappingException {
343                try {
344                        // JiBX does not support SAX natively, so we write to a buffer first, and transform that to the handlers
345                        SAXResult saxResult = new SAXResult(contentHandler);
346                        saxResult.setLexicalHandler(lexicalHandler);
347                        transformAndMarshal(graph, saxResult);
348                }
349                catch (IOException ex) {
350                        throw new MarshallingFailureException("JiBX marshalling exception", ex);
351                }
352        }
353
354        private void transformAndMarshal(Object graph, Result result) throws IOException {
355                try {
356                        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
357                        marshalOutputStream(graph, os);
358                        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
359                        Transformer transformer = this.transformerFactory.newTransformer();
360                        transformer.transform(new StreamSource(is), result);
361                }
362                catch (TransformerException ex) {
363                        throw new MarshallingFailureException(
364                                        "Could not transform to [" + ClassUtils.getShortName(result.getClass()) + "]", ex);
365                }
366
367        }
368
369
370        // Unmarshalling
371
372        @Override
373        protected Object unmarshalXmlEventReader(XMLEventReader eventReader) {
374                try {
375                        XMLStreamReader streamReader = StaxUtils.createEventStreamReader(eventReader);
376                        return unmarshalXmlStreamReader(streamReader);
377                }
378                catch (XMLStreamException ex) {
379                        return new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
380                }
381        }
382
383        @Override
384        protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) {
385                try {
386                        UnmarshallingContext unmarshallingContext = (UnmarshallingContext) createUnmarshallingContext();
387                        IXMLReader xmlReader = new StAXReaderWrapper(streamReader, null, true);
388                        unmarshallingContext.setDocument(xmlReader);
389                        return unmarshallingContext.unmarshalElement();
390                }
391                catch (JiBXException ex) {
392                        throw convertJibxException(ex, false);
393                }
394        }
395
396        @Override
397        protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException {
398                try {
399                        IUnmarshallingContext unmarshallingContext = createUnmarshallingContext();
400                        return unmarshallingContext.unmarshalDocument(inputStream, this.encoding);
401                }
402                catch (JiBXException ex) {
403                        throw convertJibxException(ex, false);
404                }
405        }
406
407        @Override
408        protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException {
409                try {
410                        IUnmarshallingContext unmarshallingContext = createUnmarshallingContext();
411                        return unmarshallingContext.unmarshalDocument(reader);
412                }
413                catch (JiBXException ex) {
414                        throw convertJibxException(ex, false);
415                }
416        }
417
418
419        // Unsupported Unmarshalling
420
421        @Override
422        protected Object unmarshalDomNode(Node node) throws XmlMappingException {
423                try {
424                        return transformAndUnmarshal(new DOMSource(node), null);
425                }
426                catch (IOException ex) {
427                        throw new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
428                }
429        }
430
431        @Override
432        protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
433                        throws XmlMappingException, IOException {
434
435                return transformAndUnmarshal(new SAXSource(xmlReader, inputSource), inputSource.getEncoding());
436        }
437
438        private Object transformAndUnmarshal(Source source, @Nullable String encoding) throws IOException {
439                try {
440                        Transformer transformer = this.transformerFactory.newTransformer();
441                        if (encoding != null) {
442                                transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
443                        }
444                        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
445                        transformer.transform(source, new StreamResult(os));
446                        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
447                        return unmarshalInputStream(is);
448                }
449                catch (TransformerException ex) {
450                        throw new MarshallingFailureException(
451                                        "Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]", ex);
452                }
453        }
454
455
456        /**
457         * Create a new {@code IMarshallingContext}, configured with the correct indentation.
458         * @return the created marshalling context
459         * @throws JiBXException in case of errors
460         */
461        protected IMarshallingContext createMarshallingContext() throws JiBXException {
462                Assert.state(this.bindingFactory != null, "JibxMarshaller not initialized");
463                IMarshallingContext marshallingContext = this.bindingFactory.createMarshallingContext();
464                marshallingContext.setIndent(this.indent);
465                return marshallingContext;
466        }
467
468        /**
469         * Create a new {@code IUnmarshallingContext}.
470         * @return the created unmarshalling context
471         * @throws JiBXException in case of errors
472         */
473        protected IUnmarshallingContext createUnmarshallingContext() throws JiBXException {
474                Assert.state(this.bindingFactory != null, "JibxMarshaller not initialized");
475                return this.bindingFactory.createUnmarshallingContext();
476        }
477
478        /**
479         * Convert the given {@code JiBXException} to an appropriate exception from the
480         * {@code org.springframework.oxm} hierarchy.
481         * <p>A boolean flag is used to indicate whether this exception occurs during marshalling or
482         * unmarshalling, since JiBX itself does not make this distinction in its exception hierarchy.
483         * @param ex {@code JiBXException} that occurred
484         * @param marshalling indicates whether the exception occurs during marshalling ({@code true}),
485         * or unmarshalling ({@code false})
486         * @return the corresponding {@code XmlMappingException}
487         */
488        public XmlMappingException convertJibxException(JiBXException ex, boolean marshalling) {
489                if (ex instanceof ValidationException) {
490                        return new ValidationFailureException("JiBX validation exception", ex);
491                }
492                else {
493                        if (marshalling) {
494                                return new MarshallingFailureException("JiBX marshalling exception", ex);
495                        }
496                        else {
497                                return new UnmarshallingFailureException("JiBX unmarshalling exception", ex);
498                        }
499                }
500        }
501
502}