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