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}