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.jaxb;
018
019import java.awt.Image;
020import java.io.ByteArrayInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.io.StringReader;
025import java.io.UnsupportedEncodingException;
026import java.lang.reflect.GenericArrayType;
027import java.lang.reflect.ParameterizedType;
028import java.lang.reflect.Type;
029import java.math.BigDecimal;
030import java.math.BigInteger;
031import java.net.URI;
032import java.net.URISyntaxException;
033import java.net.URLDecoder;
034import java.net.URLEncoder;
035import java.util.Arrays;
036import java.util.Calendar;
037import java.util.Date;
038import java.util.Map;
039import java.util.UUID;
040
041import javax.activation.DataHandler;
042import javax.activation.DataSource;
043import javax.xml.XMLConstants;
044import javax.xml.bind.JAXBContext;
045import javax.xml.bind.JAXBElement;
046import javax.xml.bind.JAXBException;
047import javax.xml.bind.MarshalException;
048import javax.xml.bind.Marshaller;
049import javax.xml.bind.UnmarshalException;
050import javax.xml.bind.Unmarshaller;
051import javax.xml.bind.ValidationEventHandler;
052import javax.xml.bind.ValidationException;
053import javax.xml.bind.annotation.XmlRootElement;
054import javax.xml.bind.annotation.adapters.XmlAdapter;
055import javax.xml.bind.attachment.AttachmentMarshaller;
056import javax.xml.bind.attachment.AttachmentUnmarshaller;
057import javax.xml.datatype.Duration;
058import javax.xml.datatype.XMLGregorianCalendar;
059import javax.xml.namespace.QName;
060import javax.xml.stream.XMLEventReader;
061import javax.xml.stream.XMLEventWriter;
062import javax.xml.stream.XMLStreamReader;
063import javax.xml.stream.XMLStreamWriter;
064import javax.xml.transform.Result;
065import javax.xml.transform.Source;
066import javax.xml.transform.dom.DOMSource;
067import javax.xml.transform.sax.SAXSource;
068import javax.xml.transform.stax.StAXSource;
069import javax.xml.transform.stream.StreamSource;
070import javax.xml.validation.Schema;
071import javax.xml.validation.SchemaFactory;
072
073import org.apache.commons.logging.Log;
074import org.apache.commons.logging.LogFactory;
075import org.w3c.dom.ls.LSResourceResolver;
076import org.xml.sax.EntityResolver;
077import org.xml.sax.InputSource;
078import org.xml.sax.SAXException;
079import org.xml.sax.XMLReader;
080
081import org.springframework.beans.factory.BeanClassLoaderAware;
082import org.springframework.beans.factory.InitializingBean;
083import org.springframework.core.annotation.AnnotationUtils;
084import org.springframework.core.io.Resource;
085import org.springframework.lang.Nullable;
086import org.springframework.oxm.GenericMarshaller;
087import org.springframework.oxm.GenericUnmarshaller;
088import org.springframework.oxm.MarshallingFailureException;
089import org.springframework.oxm.UncategorizedMappingException;
090import org.springframework.oxm.UnmarshallingFailureException;
091import org.springframework.oxm.ValidationFailureException;
092import org.springframework.oxm.XmlMappingException;
093import org.springframework.oxm.mime.MimeContainer;
094import org.springframework.oxm.mime.MimeMarshaller;
095import org.springframework.oxm.mime.MimeUnmarshaller;
096import org.springframework.oxm.support.SaxResourceUtils;
097import org.springframework.util.Assert;
098import org.springframework.util.ClassUtils;
099import org.springframework.util.FileCopyUtils;
100import org.springframework.util.ObjectUtils;
101import org.springframework.util.StringUtils;
102import org.springframework.util.xml.StaxUtils;
103
104/**
105 * Implementation of the {@code GenericMarshaller} interface for JAXB 2.2.
106 *
107 * <p>The typical usage will be to set either the "contextPath" or the "classesToBeBound"
108 * property on this bean, possibly customize the marshaller and unmarshaller by setting
109 * properties, schemas, adapters, and listeners, and to refer to it.
110 *
111 * @author Arjen Poutsma
112 * @author Juergen Hoeller
113 * @author Rossen Stoyanchev
114 * @since 3.0
115 * @see #setContextPath
116 * @see #setClassesToBeBound
117 * @see #setJaxbContextProperties
118 * @see #setMarshallerProperties
119 * @see #setUnmarshallerProperties
120 * @see #setSchema
121 * @see #setSchemas
122 * @see #setMarshallerListener
123 * @see #setUnmarshallerListener
124 * @see #setAdapters
125 */
126public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, GenericMarshaller, GenericUnmarshaller,
127                BeanClassLoaderAware, InitializingBean {
128
129        private static final String CID = "cid:";
130
131        private static final EntityResolver NO_OP_ENTITY_RESOLVER =
132                        (publicId, systemId) -> new InputSource(new StringReader(""));
133
134
135        /** Logger available to subclasses. */
136        protected final Log logger = LogFactory.getLog(getClass());
137
138        @Nullable
139        private String contextPath;
140
141        @Nullable
142        private Class<?>[] classesToBeBound;
143
144        @Nullable
145        private String[] packagesToScan;
146
147        @Nullable
148        private Map<String, ?> jaxbContextProperties;
149
150        @Nullable
151        private Map<String, ?> marshallerProperties;
152
153        @Nullable
154        private Map<String, ?> unmarshallerProperties;
155
156        @Nullable
157        private Marshaller.Listener marshallerListener;
158
159        @Nullable
160        private Unmarshaller.Listener unmarshallerListener;
161
162        @Nullable
163        private ValidationEventHandler validationEventHandler;
164
165        @Nullable
166        private XmlAdapter<?, ?>[] adapters;
167
168        @Nullable
169        private Resource[] schemaResources;
170
171        private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI;
172
173        @Nullable
174        private LSResourceResolver schemaResourceResolver;
175
176        private boolean lazyInit = false;
177
178        private boolean mtomEnabled = false;
179
180        private boolean supportJaxbElementClass = false;
181
182        private boolean checkForXmlRootElement = true;
183
184        @Nullable
185        private Class<?> mappedClass;
186
187        @Nullable
188        private ClassLoader beanClassLoader;
189
190        private final Object jaxbContextMonitor = new Object();
191
192        @Nullable
193        private volatile JAXBContext jaxbContext;
194
195        @Nullable
196        private Schema schema;
197
198        private boolean supportDtd = false;
199
200        private boolean processExternalEntities = false;
201
202
203        /**
204         * Set multiple JAXB context paths. The given array of context paths gets
205         * converted to a colon-delimited string, as supported by JAXB.
206         */
207        public void setContextPaths(String... contextPaths) {
208                Assert.notEmpty(contextPaths, "'contextPaths' must not be empty");
209                this.contextPath = StringUtils.arrayToDelimitedString(contextPaths, ":");
210        }
211
212        /**
213         * Set a JAXB context path.
214         * <p>Setting either this property, {@link #setClassesToBeBound "classesToBeBound"}
215         * or {@link #setPackagesToScan "packagesToScan"} is required.
216         */
217        public void setContextPath(@Nullable String contextPath) {
218                this.contextPath = contextPath;
219        }
220
221        /**
222         * Return the JAXB context path.
223         */
224        @Nullable
225        public String getContextPath() {
226                return this.contextPath;
227        }
228
229        /**
230         * Set the list of Java classes to be recognized by a newly created JAXBContext.
231         * <p>Setting either this property, {@link #setContextPath "contextPath"}
232         * or {@link #setPackagesToScan "packagesToScan"} is required.
233         */
234        public void setClassesToBeBound(@Nullable Class<?>... classesToBeBound) {
235                this.classesToBeBound = classesToBeBound;
236        }
237
238        /**
239         * Return the list of Java classes to be recognized by a newly created JAXBContext.
240         */
241        @Nullable
242        public Class<?>[] getClassesToBeBound() {
243                return this.classesToBeBound;
244        }
245
246        /**
247         * Set the packages to search for classes with JAXB2 annotations in the classpath.
248         * This is using a Spring-bases search and therefore analogous to Spring's component-scan
249         * feature ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
250         * <p>Setting either this property, {@link #setContextPath "contextPath"} or
251         * {@link #setClassesToBeBound "classesToBeBound"} is required.
252         */
253        public void setPackagesToScan(@Nullable String... packagesToScan) {
254                this.packagesToScan = packagesToScan;
255        }
256
257        /**
258         * Return the packages to search for JAXB2 annotations.
259         */
260        @Nullable
261        public String[] getPackagesToScan() {
262                return this.packagesToScan;
263        }
264
265        /**
266         * Set the {@code JAXBContext} properties. These implementation-specific
267         * properties will be set on the underlying {@code JAXBContext}.
268         */
269        public void setJaxbContextProperties(Map<String, ?> jaxbContextProperties) {
270                this.jaxbContextProperties = jaxbContextProperties;
271        }
272
273        /**
274         * Set the JAXB {@code Marshaller} properties.
275         * <p>These properties will be set on the underlying JAXB {@code Marshaller},
276         * and allow for features such as indentation.
277         * @param properties the properties
278         * @see javax.xml.bind.Marshaller#setProperty(String, Object)
279         * @see javax.xml.bind.Marshaller#JAXB_ENCODING
280         * @see javax.xml.bind.Marshaller#JAXB_FORMATTED_OUTPUT
281         * @see javax.xml.bind.Marshaller#JAXB_NO_NAMESPACE_SCHEMA_LOCATION
282         * @see javax.xml.bind.Marshaller#JAXB_SCHEMA_LOCATION
283         */
284        public void setMarshallerProperties(Map<String, ?> properties) {
285                this.marshallerProperties = properties;
286        }
287
288        /**
289         * Set the JAXB {@code Unmarshaller} properties.
290         * <p>These properties will be set on the underlying JAXB {@code Unmarshaller}.
291         * @param properties the properties
292         * @see javax.xml.bind.Unmarshaller#setProperty(String, Object)
293         */
294        public void setUnmarshallerProperties(Map<String, ?> properties) {
295                this.unmarshallerProperties = properties;
296        }
297
298        /**
299         * Specify the {@code Marshaller.Listener} to be registered with the JAXB {@code Marshaller}.
300         */
301        public void setMarshallerListener(Marshaller.Listener marshallerListener) {
302                this.marshallerListener = marshallerListener;
303        }
304
305        /**
306         * Set the {@code Unmarshaller.Listener} to be registered with the JAXB {@code Unmarshaller}.
307         */
308        public void setUnmarshallerListener(Unmarshaller.Listener unmarshallerListener) {
309                this.unmarshallerListener = unmarshallerListener;
310        }
311
312        /**
313         * Set the JAXB validation event handler. This event handler will be called by JAXB
314         * if any validation errors are encountered during calls to any of the marshal APIs.
315         */
316        public void setValidationEventHandler(ValidationEventHandler validationEventHandler) {
317                this.validationEventHandler = validationEventHandler;
318        }
319
320        /**
321         * Specify the {@code XmlAdapter}s to be registered with the JAXB {@code Marshaller}
322         * and {@code Unmarshaller}.
323         */
324        public void setAdapters(XmlAdapter<?, ?>... adapters) {
325                this.adapters = adapters;
326        }
327
328        /**
329         * Set the schema resource to use for validation.
330         */
331        public void setSchema(Resource schemaResource) {
332                this.schemaResources = new Resource[] {schemaResource};
333        }
334
335        /**
336         * Set the schema resources to use for validation.
337         */
338        public void setSchemas(Resource... schemaResources) {
339                this.schemaResources = schemaResources;
340        }
341
342        /**
343         * Set the schema language.
344         * Default is the W3C XML Schema: {@code http://www.w3.org/2001/XMLSchema"}.
345         * @see XMLConstants#W3C_XML_SCHEMA_NS_URI
346         * @see XMLConstants#RELAXNG_NS_URI
347         */
348        public void setSchemaLanguage(String schemaLanguage) {
349                this.schemaLanguage = schemaLanguage;
350        }
351
352        /**
353         * Set the resource resolver, as used to load the schema resources.
354         * @see SchemaFactory#setResourceResolver(org.w3c.dom.ls.LSResourceResolver)
355         * @see #setSchema
356         * @see #setSchemas
357         */
358        public void setSchemaResourceResolver(LSResourceResolver schemaResourceResolver) {
359                this.schemaResourceResolver = schemaResourceResolver;
360        }
361
362        /**
363         * Set whether to lazily initialize the {@link JAXBContext} for this marshaller.
364         * Default is {@code false} to initialize on startup; can be switched to {@code true}.
365         * <p>Early initialization just applies if {@link #afterPropertiesSet()} is called.
366         */
367        public void setLazyInit(boolean lazyInit) {
368                this.lazyInit = lazyInit;
369        }
370
371        /**
372         * Specify whether MTOM support should be enabled or not.
373         * Default is {@code false}: marshalling using XOP/MTOM not being enabled.
374         */
375        public void setMtomEnabled(boolean mtomEnabled) {
376                this.mtomEnabled = mtomEnabled;
377        }
378
379        /**
380         * Specify whether the {@link #supports(Class)} returns {@code true} for the
381         * {@link JAXBElement} class.
382         * <p>Default is {@code false}, meaning that {@code supports(Class)} always returns
383         * {@code false} for {@code JAXBElement} classes (though {@link #supports(Type)} can
384         * return {@code true}, since it can obtain the type parameters of {@code JAXBElement}).
385         * <p>This property is typically enabled in combination with usage of classes like
386         * {@link org.springframework.web.servlet.view.xml.MarshallingView MarshallingView},
387         * since the {@code ModelAndView} does not offer type parameter information at runtime.
388         * @see #supports(Class)
389         * @see #supports(Type)
390         */
391        public void setSupportJaxbElementClass(boolean supportJaxbElementClass) {
392                this.supportJaxbElementClass = supportJaxbElementClass;
393        }
394
395        /**
396         * Specify whether the {@link #supports(Class)} should check for
397         * {@link XmlRootElement @XmlRootElement} annotations.
398         * <p>Default is {@code true}, meaning that {@code supports(Class)} will check for
399         * this annotation. However, some JAXB implementations (i.e. EclipseLink MOXy) allow
400         * for defining the bindings in an external definition file, thus keeping the classes
401         * annotations free. Setting this property to {@code false} supports these
402         * JAXB implementations.
403         * @see #supports(Class)
404         * @see #supports(Type)
405         */
406        public void setCheckForXmlRootElement(boolean checkForXmlRootElement) {
407                this.checkForXmlRootElement = checkForXmlRootElement;
408        }
409
410        /**
411         * Specify a JAXB mapped class for partial unmarshalling.
412         * @see javax.xml.bind.Unmarshaller#unmarshal(javax.xml.transform.Source, Class)
413         */
414        public void setMappedClass(Class<?> mappedClass) {
415                this.mappedClass = mappedClass;
416        }
417
418        /**
419         * Indicate whether DTD parsing should be supported.
420         * <p>Default is {@code false} meaning that DTD is disabled.
421         */
422        public void setSupportDtd(boolean supportDtd) {
423                this.supportDtd = supportDtd;
424        }
425
426        /**
427         * Return whether DTD parsing is supported.
428         */
429        public boolean isSupportDtd() {
430                return this.supportDtd;
431        }
432
433        /**
434         * Indicate whether external XML entities are processed when unmarshalling.
435         * <p>Default is {@code false}, meaning that external entities are not resolved.
436         * Note that processing of external entities will only be enabled/disabled when the
437         * {@code Source} passed to {@link #unmarshal(Source)} is a {@link SAXSource} or
438         * {@link StreamSource}. It has no effect for {@link DOMSource} or {@link StAXSource}
439         * instances.
440         * <p><strong>Note:</strong> setting this option to {@code true} also automatically
441         * sets {@link #setSupportDtd} to {@code true}.
442         */
443        public void setProcessExternalEntities(boolean processExternalEntities) {
444                this.processExternalEntities = processExternalEntities;
445                if (processExternalEntities) {
446                        this.supportDtd = true;
447                }
448        }
449
450        /**
451         * Return whether XML external entities are allowed.
452         */
453        public boolean isProcessExternalEntities() {
454                return this.processExternalEntities;
455        }
456
457
458        @Override
459        public void setBeanClassLoader(ClassLoader classLoader) {
460                this.beanClassLoader = classLoader;
461        }
462
463
464        @Override
465        public void afterPropertiesSet() throws Exception {
466                boolean hasContextPath = StringUtils.hasLength(this.contextPath);
467                boolean hasClassesToBeBound = !ObjectUtils.isEmpty(this.classesToBeBound);
468                boolean hasPackagesToScan = !ObjectUtils.isEmpty(this.packagesToScan);
469
470                if (hasContextPath && (hasClassesToBeBound || hasPackagesToScan) ||
471                                (hasClassesToBeBound && hasPackagesToScan)) {
472                        throw new IllegalArgumentException("Specify either 'contextPath', 'classesToBeBound', " +
473                                        "or 'packagesToScan'");
474                }
475                if (!hasContextPath && !hasClassesToBeBound && !hasPackagesToScan) {
476                        throw new IllegalArgumentException(
477                                        "Setting either 'contextPath', 'classesToBeBound', " + "or 'packagesToScan' is required");
478                }
479                if (!this.lazyInit) {
480                        getJaxbContext();
481                }
482                if (!ObjectUtils.isEmpty(this.schemaResources)) {
483                        this.schema = loadSchema(this.schemaResources, this.schemaLanguage);
484                }
485        }
486
487        /**
488         * Return the JAXBContext used by this marshaller, lazily building it if necessary.
489         */
490        public JAXBContext getJaxbContext() {
491                JAXBContext context = this.jaxbContext;
492                if (context != null) {
493                        return context;
494                }
495                synchronized (this.jaxbContextMonitor) {
496                        context = this.jaxbContext;
497                        if (context == null) {
498                                try {
499                                        if (StringUtils.hasLength(this.contextPath)) {
500                                                context = createJaxbContextFromContextPath(this.contextPath);
501                                        }
502                                        else if (!ObjectUtils.isEmpty(this.classesToBeBound)) {
503                                                context = createJaxbContextFromClasses(this.classesToBeBound);
504                                        }
505                                        else if (!ObjectUtils.isEmpty(this.packagesToScan)) {
506                                                context = createJaxbContextFromPackages(this.packagesToScan);
507                                        }
508                                        else {
509                                                context = JAXBContext.newInstance();
510                                        }
511                                        this.jaxbContext = context;
512                                }
513                                catch (JAXBException ex) {
514                                        throw convertJaxbException(ex);
515                                }
516                        }
517                        return context;
518                }
519        }
520
521        private JAXBContext createJaxbContextFromContextPath(String contextPath) throws JAXBException {
522                if (logger.isDebugEnabled()) {
523                        logger.debug("Creating JAXBContext with context path [" + this.contextPath + "]");
524                }
525                if (this.jaxbContextProperties != null) {
526                        if (this.beanClassLoader != null) {
527                                return JAXBContext.newInstance(contextPath, this.beanClassLoader, this.jaxbContextProperties);
528                        }
529                        else {
530                                // analogous to the JAXBContext.newInstance(String) implementation
531                                return JAXBContext.newInstance(contextPath, Thread.currentThread().getContextClassLoader(),
532                                                this.jaxbContextProperties);
533                        }
534                }
535                else {
536                        if (this.beanClassLoader != null) {
537                                return JAXBContext.newInstance(contextPath, this.beanClassLoader);
538                        }
539                        else {
540                                return JAXBContext.newInstance(contextPath);
541                        }
542                }
543        }
544
545        private JAXBContext createJaxbContextFromClasses(Class<?>... classesToBeBound) throws JAXBException {
546                if (logger.isDebugEnabled()) {
547                        logger.debug("Creating JAXBContext with classes to be bound [" +
548                                        StringUtils.arrayToCommaDelimitedString(classesToBeBound) + "]");
549                }
550                if (this.jaxbContextProperties != null) {
551                        return JAXBContext.newInstance(classesToBeBound, this.jaxbContextProperties);
552                }
553                else {
554                        return JAXBContext.newInstance(classesToBeBound);
555                }
556        }
557
558        private JAXBContext createJaxbContextFromPackages(String... packagesToScan) throws JAXBException {
559                if (logger.isDebugEnabled()) {
560                        logger.debug("Creating JAXBContext by scanning packages [" +
561                                        StringUtils.arrayToCommaDelimitedString(packagesToScan) + "]");
562                }
563                ClassPathJaxb2TypeScanner scanner = new ClassPathJaxb2TypeScanner(this.beanClassLoader, packagesToScan);
564                Class<?>[] jaxb2Classes = scanner.scanPackages();
565                if (logger.isDebugEnabled()) {
566                        logger.debug("Found JAXB2 classes: [" + StringUtils.arrayToCommaDelimitedString(jaxb2Classes) + "]");
567                }
568                this.classesToBeBound = jaxb2Classes;
569                if (this.jaxbContextProperties != null) {
570                        return JAXBContext.newInstance(jaxb2Classes, this.jaxbContextProperties);
571                }
572                else {
573                        return JAXBContext.newInstance(jaxb2Classes);
574                }
575        }
576
577        @SuppressWarnings("deprecation")  // on JDK 9
578        private Schema loadSchema(Resource[] resources, String schemaLanguage) throws IOException, SAXException {
579                if (logger.isDebugEnabled()) {
580                        logger.debug("Setting validation schema to " +
581                                        StringUtils.arrayToCommaDelimitedString(this.schemaResources));
582                }
583                Assert.notEmpty(resources, "No resources given");
584                Assert.hasLength(schemaLanguage, "No schema language provided");
585                Source[] schemaSources = new Source[resources.length];
586                XMLReader xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
587                xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
588                for (int i = 0; i < resources.length; i++) {
589                        Resource resource = resources[i];
590                        Assert.isTrue(resource != null && resource.exists(), () -> "Resource does not exist: " + resource);
591                        InputSource inputSource = SaxResourceUtils.createInputSource(resource);
592                        schemaSources[i] = new SAXSource(xmlReader, inputSource);
593                }
594                SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage);
595                if (this.schemaResourceResolver != null) {
596                        schemaFactory.setResourceResolver(this.schemaResourceResolver);
597                }
598                return schemaFactory.newSchema(schemaSources);
599        }
600
601
602        @Override
603        public boolean supports(Class<?> clazz) {
604                return (this.supportJaxbElementClass && JAXBElement.class.isAssignableFrom(clazz)) ||
605                                supportsInternal(clazz, this.checkForXmlRootElement);
606        }
607
608        @Override
609        public boolean supports(Type genericType) {
610                if (genericType instanceof ParameterizedType) {
611                        ParameterizedType parameterizedType = (ParameterizedType) genericType;
612                        if (JAXBElement.class == parameterizedType.getRawType() &&
613                                        parameterizedType.getActualTypeArguments().length == 1) {
614                                Type typeArgument = parameterizedType.getActualTypeArguments()[0];
615                                if (typeArgument instanceof Class) {
616                                        Class<?> classArgument = (Class<?>) typeArgument;
617                                        return ((classArgument.isArray() && Byte.TYPE == classArgument.getComponentType()) ||
618                                                        isPrimitiveWrapper(classArgument) || isStandardClass(classArgument) ||
619                                                        supportsInternal(classArgument, false));
620                                }
621                                else if (typeArgument instanceof GenericArrayType) {
622                                        GenericArrayType arrayType = (GenericArrayType) typeArgument;
623                                        return (Byte.TYPE == arrayType.getGenericComponentType());
624                                }
625                        }
626                }
627                else if (genericType instanceof Class) {
628                        Class<?> clazz = (Class<?>) genericType;
629                        return supportsInternal(clazz, this.checkForXmlRootElement);
630                }
631                return false;
632        }
633
634        private boolean supportsInternal(Class<?> clazz, boolean checkForXmlRootElement) {
635                if (checkForXmlRootElement && AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) == null) {
636                        return false;
637                }
638                if (StringUtils.hasLength(this.contextPath)) {
639                        String packageName = ClassUtils.getPackageName(clazz);
640                        String[] contextPaths = StringUtils.tokenizeToStringArray(this.contextPath, ":");
641                        for (String contextPath : contextPaths) {
642                                if (contextPath.equals(packageName)) {
643                                        return true;
644                                }
645                        }
646                        return false;
647                }
648                else if (!ObjectUtils.isEmpty(this.classesToBeBound)) {
649                        return Arrays.asList(this.classesToBeBound).contains(clazz);
650                }
651                return false;
652        }
653
654        /**
655         * Checks whether the given type is a primitive wrapper type.
656         * Compare section 8.5.1 of the JAXB2 spec.
657         */
658        private boolean isPrimitiveWrapper(Class<?> clazz) {
659                return (Boolean.class == clazz ||
660                                Byte.class == clazz ||
661                                Short.class == clazz ||
662                                Integer.class == clazz ||
663                                Long.class == clazz ||
664                                Float.class == clazz ||
665                                Double.class == clazz);
666        }
667
668        /**
669         * Checks whether the given type is a standard class.
670         * Compare section 8.5.2 of the JAXB2 spec.
671         */
672        private boolean isStandardClass(Class<?> clazz) {
673                return (String.class == clazz ||
674                                BigInteger.class.isAssignableFrom(clazz) ||
675                                BigDecimal.class.isAssignableFrom(clazz) ||
676                                Calendar.class.isAssignableFrom(clazz) ||
677                                Date.class.isAssignableFrom(clazz) ||
678                                QName.class.isAssignableFrom(clazz) ||
679                                URI.class == clazz ||
680                                XMLGregorianCalendar.class.isAssignableFrom(clazz) ||
681                                Duration.class.isAssignableFrom(clazz) ||
682                                Image.class == clazz ||
683                                DataHandler.class == clazz ||
684                                // Source and subclasses should be supported according to the JAXB2 spec, but aren't in the RI
685                                // Source.class.isAssignableFrom(clazz) ||
686                                UUID.class == clazz);
687
688        }
689
690
691        // Marshalling
692
693        @Override
694        public void marshal(Object graph, Result result) throws XmlMappingException {
695                marshal(graph, result, null);
696        }
697
698        @Override
699        public void marshal(Object graph, Result result, @Nullable MimeContainer mimeContainer) throws XmlMappingException {
700                try {
701                        Marshaller marshaller = createMarshaller();
702                        if (this.mtomEnabled && mimeContainer != null) {
703                                marshaller.setAttachmentMarshaller(new Jaxb2AttachmentMarshaller(mimeContainer));
704                        }
705                        if (StaxUtils.isStaxResult(result)) {
706                                marshalStaxResult(marshaller, graph, result);
707                        }
708                        else {
709                                marshaller.marshal(graph, result);
710                        }
711                }
712                catch (JAXBException ex) {
713                        throw convertJaxbException(ex);
714                }
715        }
716
717        /**
718         * Return a newly created JAXB marshaller.
719         * <p>Note: JAXB marshallers are not necessarily thread-safe.
720         * This method is public as of 5.2.
721         * @since 5.2
722         * @see #createUnmarshaller()
723         */
724        public Marshaller createMarshaller() {
725                try {
726                        Marshaller marshaller = getJaxbContext().createMarshaller();
727                        initJaxbMarshaller(marshaller);
728                        return marshaller;
729                }
730                catch (JAXBException ex) {
731                        throw convertJaxbException(ex);
732                }
733        }
734
735        private void marshalStaxResult(Marshaller jaxbMarshaller, Object graph, Result staxResult) throws JAXBException {
736                XMLStreamWriter streamWriter = StaxUtils.getXMLStreamWriter(staxResult);
737                if (streamWriter != null) {
738                        jaxbMarshaller.marshal(graph, streamWriter);
739                }
740                else {
741                        XMLEventWriter eventWriter = StaxUtils.getXMLEventWriter(staxResult);
742                        if (eventWriter != null) {
743                                jaxbMarshaller.marshal(graph, eventWriter);
744                        }
745                        else {
746                                throw new IllegalArgumentException("StAX Result contains neither XMLStreamWriter nor XMLEventConsumer");
747                        }
748                }
749        }
750
751        /**
752         * Template method that can be overridden by concrete JAXB marshallers
753         * for custom initialization behavior. Gets called after creation of JAXB
754         * {@code Marshaller}, and after the respective properties have been set.
755         * <p>The default implementation sets the
756         * {@link #setMarshallerProperties defined properties}, the
757         * {@link #setValidationEventHandler validation event handler}, the
758         * {@link #setSchemas schemas}, {@link #setMarshallerListener listener},
759         * and {@link #setAdapters adapters}.
760         */
761        protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException {
762                if (this.marshallerProperties != null) {
763                        for (Map.Entry<String, ?> entry : this.marshallerProperties.entrySet()) {
764                                marshaller.setProperty(entry.getKey(), entry.getValue());
765                        }
766                }
767                if (this.marshallerListener != null) {
768                        marshaller.setListener(this.marshallerListener);
769                }
770                if (this.validationEventHandler != null) {
771                        marshaller.setEventHandler(this.validationEventHandler);
772                }
773                if (this.adapters != null) {
774                        for (XmlAdapter<?, ?> adapter : this.adapters) {
775                                marshaller.setAdapter(adapter);
776                        }
777                }
778                if (this.schema != null) {
779                        marshaller.setSchema(this.schema);
780                }
781        }
782
783
784        // Unmarshalling
785
786        @Override
787        public Object unmarshal(Source source) throws XmlMappingException {
788                return unmarshal(source, null);
789        }
790
791        @Override
792        public Object unmarshal(Source source, @Nullable MimeContainer mimeContainer) throws XmlMappingException {
793                source = processSource(source);
794
795                try {
796                        Unmarshaller unmarshaller = createUnmarshaller();
797                        if (this.mtomEnabled && mimeContainer != null) {
798                                unmarshaller.setAttachmentUnmarshaller(new Jaxb2AttachmentUnmarshaller(mimeContainer));
799                        }
800                        if (StaxUtils.isStaxSource(source)) {
801                                return unmarshalStaxSource(unmarshaller, source);
802                        }
803                        else if (this.mappedClass != null) {
804                                return unmarshaller.unmarshal(source, this.mappedClass).getValue();
805                        }
806                        else {
807                                return unmarshaller.unmarshal(source);
808                        }
809                }
810                catch (NullPointerException ex) {
811                        if (!isSupportDtd()) {
812                                throw new UnmarshallingFailureException("NPE while unmarshalling: " +
813                                                "This can happen due to the presence of DTD declarations which are disabled.", ex);
814                        }
815                        throw ex;
816                }
817                catch (JAXBException ex) {
818                        throw convertJaxbException(ex);
819                }
820        }
821
822        /**
823         * Return a newly created JAXB unmarshaller.
824         * <p>Note: JAXB unmarshallers are not necessarily thread-safe.
825         * This method is public as of 5.2.
826         * @since 5.2
827         * @see #createMarshaller()
828         */
829        public Unmarshaller createUnmarshaller() {
830                try {
831                        Unmarshaller unmarshaller = getJaxbContext().createUnmarshaller();
832                        initJaxbUnmarshaller(unmarshaller);
833                        return unmarshaller;
834                }
835                catch (JAXBException ex) {
836                        throw convertJaxbException(ex);
837                }
838        }
839
840        protected Object unmarshalStaxSource(Unmarshaller jaxbUnmarshaller, Source staxSource) throws JAXBException {
841                XMLStreamReader streamReader = StaxUtils.getXMLStreamReader(staxSource);
842                if (streamReader != null) {
843                        return (this.mappedClass != null ?
844                                        jaxbUnmarshaller.unmarshal(streamReader, this.mappedClass).getValue() :
845                                        jaxbUnmarshaller.unmarshal(streamReader));
846                }
847                else {
848                        XMLEventReader eventReader = StaxUtils.getXMLEventReader(staxSource);
849                        if (eventReader != null) {
850                                return (this.mappedClass != null ?
851                                                jaxbUnmarshaller.unmarshal(eventReader, this.mappedClass).getValue() :
852                                                jaxbUnmarshaller.unmarshal(eventReader));
853                        }
854                        else {
855                                throw new IllegalArgumentException("StaxSource contains neither XMLStreamReader nor XMLEventReader");
856                        }
857                }
858        }
859
860        @SuppressWarnings("deprecation")  // on JDK 9
861        private Source processSource(Source source) {
862                if (StaxUtils.isStaxSource(source) || source instanceof DOMSource) {
863                        return source;
864                }
865
866                XMLReader xmlReader = null;
867                InputSource inputSource = null;
868
869                if (source instanceof SAXSource) {
870                        SAXSource saxSource = (SAXSource) source;
871                        xmlReader = saxSource.getXMLReader();
872                        inputSource = saxSource.getInputSource();
873                }
874                else if (source instanceof StreamSource) {
875                        StreamSource streamSource = (StreamSource) source;
876                        if (streamSource.getInputStream() != null) {
877                                inputSource = new InputSource(streamSource.getInputStream());
878                        }
879                        else if (streamSource.getReader() != null) {
880                                inputSource = new InputSource(streamSource.getReader());
881                        }
882                        else {
883                                inputSource = new InputSource(streamSource.getSystemId());
884                        }
885                }
886
887                try {
888                        if (xmlReader == null) {
889                                xmlReader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
890                        }
891                        xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
892                        String name = "http://xml.org/sax/features/external-general-entities";
893                        xmlReader.setFeature(name, isProcessExternalEntities());
894                        if (!isProcessExternalEntities()) {
895                                xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
896                        }
897                        return new SAXSource(xmlReader, inputSource);
898                }
899                catch (SAXException ex) {
900                        logger.info("Processing of external entities could not be disabled", ex);
901                        return source;
902                }
903        }
904
905        /**
906         * Template method that can be overridden by concrete JAXB marshallers
907         * for custom initialization behavior. Gets called after creation of JAXB
908         * {@code Marshaller}, and after the respective properties have been set.
909         * <p>The default implementation sets the
910         * {@link #setUnmarshallerProperties defined properties}, the
911         * {@link #setValidationEventHandler validation event handler}, the
912         * {@link #setSchemas schemas}, {@link #setUnmarshallerListener listener},
913         * and {@link #setAdapters adapters}.
914         */
915        protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBException {
916                if (this.unmarshallerProperties != null) {
917                        for (Map.Entry<String, ?> entry : this.unmarshallerProperties.entrySet()) {
918                                unmarshaller.setProperty(entry.getKey(), entry.getValue());
919                        }
920                }
921                if (this.unmarshallerListener != null) {
922                        unmarshaller.setListener(this.unmarshallerListener);
923                }
924                if (this.validationEventHandler != null) {
925                        unmarshaller.setEventHandler(this.validationEventHandler);
926                }
927                if (this.adapters != null) {
928                        for (XmlAdapter<?, ?> adapter : this.adapters) {
929                                unmarshaller.setAdapter(adapter);
930                        }
931                }
932                if (this.schema != null) {
933                        unmarshaller.setSchema(this.schema);
934                }
935        }
936
937        /**
938         * Convert the given {@code JAXBException} to an appropriate exception
939         * from the {@code org.springframework.oxm} hierarchy.
940         * @param ex {@code JAXBException} that occurred
941         * @return the corresponding {@code XmlMappingException}
942         */
943        protected XmlMappingException convertJaxbException(JAXBException ex) {
944                if (ex instanceof ValidationException) {
945                        return new ValidationFailureException("JAXB validation exception", ex);
946                }
947                else if (ex instanceof MarshalException) {
948                        return new MarshallingFailureException("JAXB marshalling exception", ex);
949                }
950                else if (ex instanceof UnmarshalException) {
951                        return new UnmarshallingFailureException("JAXB unmarshalling exception", ex);
952                }
953                else {
954                        // fallback
955                        return new UncategorizedMappingException("Unknown JAXB exception", ex);
956                }
957        }
958
959
960        private static class Jaxb2AttachmentMarshaller extends AttachmentMarshaller {
961
962                private final MimeContainer mimeContainer;
963
964                public Jaxb2AttachmentMarshaller(MimeContainer mimeContainer) {
965                        this.mimeContainer = mimeContainer;
966                }
967
968                @Override
969                public String addMtomAttachment(byte[] data, int offset, int length, String mimeType,
970                                String elementNamespace, String elementLocalName) {
971                        ByteArrayDataSource dataSource = new ByteArrayDataSource(mimeType, data, offset, length);
972                        return addMtomAttachment(new DataHandler(dataSource), elementNamespace, elementLocalName);
973                }
974
975                @Override
976                public String addMtomAttachment(DataHandler dataHandler, String elementNamespace, String elementLocalName) {
977                        String host = getHost(elementNamespace, dataHandler);
978                        String contentId = UUID.randomUUID() + "@" + host;
979                        this.mimeContainer.addAttachment("<" + contentId + ">", dataHandler);
980                        try {
981                                contentId = URLEncoder.encode(contentId, "UTF-8");
982                        }
983                        catch (UnsupportedEncodingException ex) {
984                                // ignore
985                        }
986                        return CID + contentId;
987                }
988
989                private String getHost(String elementNamespace, DataHandler dataHandler) {
990                        try {
991                                URI uri = new URI(elementNamespace);
992                                return uri.getHost();
993                        }
994                        catch (URISyntaxException ex) {
995                                // ignore
996                        }
997                        return dataHandler.getName();
998                }
999
1000                @Override
1001                public String addSwaRefAttachment(DataHandler dataHandler) {
1002                        String contentId = UUID.randomUUID() + "@" + dataHandler.getName();
1003                        this.mimeContainer.addAttachment(contentId, dataHandler);
1004                        return contentId;
1005                }
1006
1007                @Override
1008                public boolean isXOPPackage() {
1009                        return this.mimeContainer.convertToXopPackage();
1010                }
1011        }
1012
1013
1014        private static class Jaxb2AttachmentUnmarshaller extends AttachmentUnmarshaller {
1015
1016                private final MimeContainer mimeContainer;
1017
1018                public Jaxb2AttachmentUnmarshaller(MimeContainer mimeContainer) {
1019                        this.mimeContainer = mimeContainer;
1020                }
1021
1022                @Override
1023                public byte[] getAttachmentAsByteArray(String cid) {
1024                        try {
1025                                DataHandler dataHandler = getAttachmentAsDataHandler(cid);
1026                                return FileCopyUtils.copyToByteArray(dataHandler.getInputStream());
1027                        }
1028                        catch (IOException ex) {
1029                                throw new UnmarshallingFailureException("Could not read attachment", ex);
1030                        }
1031                }
1032
1033                @Override
1034                public DataHandler getAttachmentAsDataHandler(String contentId) {
1035                        if (contentId.startsWith(CID)) {
1036                                contentId = contentId.substring(CID.length());
1037                                try {
1038                                        contentId = URLDecoder.decode(contentId, "UTF-8");
1039                                }
1040                                catch (UnsupportedEncodingException ex) {
1041                                        // ignore
1042                                }
1043                                contentId = '<' + contentId + '>';
1044                        }
1045                        DataHandler dataHandler = this.mimeContainer.getAttachment(contentId);
1046                        if (dataHandler == null) {
1047                                throw new IllegalArgumentException("No attachment found for " + contentId);
1048                        }
1049                        return dataHandler;
1050                }
1051
1052                @Override
1053                public boolean isXOPPackage() {
1054                        return this.mimeContainer.isXopPackage();
1055                }
1056        }
1057
1058
1059        /**
1060         * DataSource that wraps around a byte array.
1061         */
1062        private static class ByteArrayDataSource implements DataSource {
1063
1064                private final byte[] data;
1065
1066                private final String contentType;
1067
1068                private final int offset;
1069
1070                private final int length;
1071
1072                public ByteArrayDataSource(String contentType, byte[] data, int offset, int length) {
1073                        this.contentType = contentType;
1074                        this.data = data;
1075                        this.offset = offset;
1076                        this.length = length;
1077                }
1078
1079                @Override
1080                public InputStream getInputStream() {
1081                        return new ByteArrayInputStream(this.data, this.offset, this.length);
1082                }
1083
1084                @Override
1085                public OutputStream getOutputStream() {
1086                        throw new UnsupportedOperationException();
1087                }
1088
1089                @Override
1090                public String getContentType() {
1091                        return this.contentType;
1092                }
1093
1094                @Override
1095                public String getName() {
1096                        return "ByteArrayDataSource";
1097                }
1098        }
1099
1100}