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}