
org.springframework.oxm.jaxb.Jaxb2Marshaller Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.oxm.jaxb;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.attachment.AttachmentMarshaller;
import javax.xml.bind.attachment.AttachmentUnmarshaller;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.validation.Schema;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.oxm.GenericMarshaller;
import org.springframework.oxm.GenericUnmarshaller;
import org.springframework.oxm.XmlMappingException;
import org.springframework.oxm.mime.MimeContainer;
import org.springframework.oxm.mime.MimeMarshaller;
import org.springframework.oxm.mime.MimeUnmarshaller;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.xml.transform.TraxUtils;
import org.springframework.xml.validation.SchemaLoaderUtils;
/**
* Implementation of the Marshaller
interface for JAXB 2.0.
*
* The typical usage will be to set either the contextPath
or the classesToBeBound
property on
* this bean, possibly customize the marshaller and unmarshaller by setting properties, schemas, adapters, and
* listeners, and to refer to it.
*
* @author Arjen Poutsma
* @see #setContextPath(String)
* @see #setClassesToBeBound(Class[])
* @see #setJaxbContextProperties(java.util.Map)
* @see #setMarshallerProperties(java.util.Map)
* @see #setUnmarshallerProperties(java.util.Map)
* @see #setSchema(org.springframework.core.io.Resource)
* @see #setSchemas(org.springframework.core.io.Resource[])
* @see #setMarshallerListener(javax.xml.bind.Marshaller.Listener)
* @see #setUnmarshallerListener(javax.xml.bind.Unmarshaller.Listener)
* @see #setAdapters(javax.xml.bind.annotation.adapters.XmlAdapter[])
* @since 1.0.0
*/
public class Jaxb2Marshaller extends AbstractJaxbMarshaller
implements MimeMarshaller, MimeUnmarshaller, GenericMarshaller, GenericUnmarshaller, BeanClassLoaderAware {
private ClassLoader classLoader;
private Resource[] schemaResources;
private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI;
private Marshaller.Listener marshallerListener;
private Unmarshaller.Listener unmarshallerListener;
private XmlAdapter[] adapters;
private Schema schema;
private Class[] classesToBeBound;
private Map jaxbContextProperties;
private boolean mtomEnabled = false;
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Sets the XmlAdapter
s to be registered with the JAXB Marshaller
and
* Unmarshaller
*/
public void setAdapters(XmlAdapter[] adapters) {
this.adapters = adapters;
}
/**
* Sets the list of java classes to be recognized by a newly created JAXBContext. Setting this property or
* contextPath
is required.
*
* @see #setContextPath(String)
*/
public void setClassesToBeBound(Class[] classesToBeBound) {
this.classesToBeBound = classesToBeBound;
}
/**
* Sets the JAXBContext
properties. These implementation-specific properties will be set on the
* JAXBContext
.
*/
public void setJaxbContextProperties(Map jaxbContextProperties) {
this.jaxbContextProperties = jaxbContextProperties;
}
/** Sets the Marshaller.Listener
to be registered with the JAXB Marshaller
. */
public void setMarshallerListener(Marshaller.Listener marshallerListener) {
this.marshallerListener = marshallerListener;
}
/**
* Indicates whether MTOM support should be enabled or not. Default is false
, marshalling using
* XOP/MTOM is not enabled.
*/
public void setMtomEnabled(boolean mtomEnabled) {
this.mtomEnabled = mtomEnabled;
}
/**
* Sets the schema language. Default is the W3C XML Schema: http://www.w3.org/2001/XMLSchema"
.
*
* @see XMLConstants#W3C_XML_SCHEMA_NS_URI
* @see XMLConstants#RELAXNG_NS_URI
*/
public void setSchemaLanguage(String schemaLanguage) {
this.schemaLanguage = schemaLanguage;
}
/** Sets the schema resource to use for validation. */
public void setSchema(Resource schemaResource) {
schemaResources = new Resource[]{schemaResource};
}
/** Sets the schema resources to use for validation. */
public void setSchemas(Resource[] schemaResources) {
this.schemaResources = schemaResources;
}
/** Sets the Unmarshaller.Listener
to be registered with the JAXB Unmarshaller
. */
public void setUnmarshallerListener(Unmarshaller.Listener unmarshallerListener) {
this.unmarshallerListener = unmarshallerListener;
}
public boolean supports(Type type) {
if (type instanceof Class) {
return supportsInternal((Class) type, true);
}
else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
if (JAXBElement.class.equals(parameterizedType.getRawType())) {
Assert.isTrue(parameterizedType.getActualTypeArguments().length == 1,
"Invalid amount of parameterized types in JAXBElement");
Type typeArgument = parameterizedType.getActualTypeArguments()[0];
if (typeArgument instanceof Class) {
Class clazz = (Class) typeArgument;
if (!isPrimitiveType(clazz) && !isStandardType(clazz) && !supportsInternal(clazz, false)) {
return false;
}
}
else if (typeArgument instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType) typeArgument;
return genericArrayType.getGenericComponentType().equals(Byte.TYPE);
}
else if (!supports(typeArgument)) {
return false;
}
return true;
}
}
return false;
}
private boolean isPrimitiveType(Class clazz) {
return (Boolean.class.equals(clazz) || Byte.class.equals(clazz) || Short.class.equals(clazz) ||
Integer.class.equals(clazz) || Long.class.equals(clazz) || Float.class.equals(clazz) ||
Double.class.equals(clazz) || byte[].class.equals(clazz));
}
private boolean isStandardType(Class clazz) {
return (String.class.equals(clazz) || BigInteger.class.equals(clazz) || BigDecimal.class.equals(clazz) ||
Calendar.class.isAssignableFrom(clazz) || Date.class.isAssignableFrom(clazz) ||
QName.class.equals(clazz) || URI.class.equals(clazz) ||
XMLGregorianCalendar.class.isAssignableFrom(clazz) || Duration.class.isAssignableFrom(clazz) ||
Object.class.equals(clazz) || Image.class.isAssignableFrom(clazz) || DataHandler.class.equals(clazz) ||
Source.class.isAssignableFrom(clazz) || UUID.class.equals(clazz));
}
public boolean supports(Class clazz) {
return supportsInternal(clazz, true);
}
private boolean supportsInternal(Class> clazz, boolean checkForXmlRootElement) {
if (checkForXmlRootElement && clazz.getAnnotation(XmlRootElement.class) == null) {
return false;
}
if (clazz.getAnnotation(XmlType.class) == null) {
return false;
}
if (StringUtils.hasLength(getContextPath())) {
String className = ClassUtils.getQualifiedName(clazz);
int lastDotIndex = className.lastIndexOf('.');
if (lastDotIndex == -1) {
return false;
}
String packageName = className.substring(0, lastDotIndex);
String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":");
for (String contextPath : contextPaths) {
if (contextPath.equals(packageName)) {
return true;
}
}
return false;
}
else if (!ObjectUtils.isEmpty(classesToBeBound)) {
return Arrays.asList(classesToBeBound).contains(clazz);
}
return false;
}
/*
* JAXBContext
*/
@Override
protected JAXBContext createJaxbContext() throws Exception {
if (JaxbUtils.getJaxbVersion(classLoader) < JaxbUtils.JAXB_2) {
throw new IllegalStateException(
"Cannot use Jaxb2Marshaller in combination with JAXB 1.0. Use Jaxb1Marshaller instead.");
}
if (StringUtils.hasLength(getContextPath()) && !ObjectUtils.isEmpty(classesToBeBound)) {
throw new IllegalArgumentException("specify either contextPath or classesToBeBound property; not both");
}
if (!ObjectUtils.isEmpty(schemaResources)) {
if (logger.isDebugEnabled()) {
logger.debug(
"Setting validation schema to " + StringUtils.arrayToCommaDelimitedString(schemaResources));
}
schema = SchemaLoaderUtils.loadSchema(schemaResources, schemaLanguage);
}
if (StringUtils.hasLength(getContextPath())) {
return createJaxbContextFromContextPath();
}
else if (!ObjectUtils.isEmpty(classesToBeBound)) {
return createJaxbContextFromClasses();
}
else {
throw new IllegalArgumentException("setting either contextPath or classesToBeBound is required");
}
}
private JAXBContext createJaxbContextFromContextPath() throws JAXBException {
if (logger.isInfoEnabled()) {
logger.info("Creating JAXBContext with context path [" + getContextPath() + "]");
}
if (jaxbContextProperties != null) {
if (classLoader != null) {
return JAXBContext
.newInstance(getContextPath(), classLoader, jaxbContextProperties);
}
else {
return JAXBContext
.newInstance(getContextPath(), ClassUtils.getDefaultClassLoader(), jaxbContextProperties);
}
}
else {
return classLoader != null ? JAXBContext.newInstance(getContextPath(), classLoader) :
JAXBContext.newInstance(getContextPath());
}
}
private JAXBContext createJaxbContextFromClasses() throws JAXBException {
if (logger.isInfoEnabled()) {
logger.info("Creating JAXBContext with classes to be bound [" +
StringUtils.arrayToCommaDelimitedString(classesToBeBound) + "]");
}
if (jaxbContextProperties != null) {
return JAXBContext.newInstance(classesToBeBound, jaxbContextProperties);
}
else {
return JAXBContext.newInstance(classesToBeBound);
}
}
/*
* Marshaller/Unmarshaller
*/
@Override
protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException {
if (schema != null) {
marshaller.setSchema(schema);
}
if (marshallerListener != null) {
marshaller.setListener(marshallerListener);
}
if (adapters != null) {
for (XmlAdapter adapter : adapters) {
marshaller.setAdapter(adapter);
}
}
}
@Override
protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBException {
if (schema != null) {
unmarshaller.setSchema(schema);
}
if (unmarshallerListener != null) {
unmarshaller.setListener(unmarshallerListener);
}
if (adapters != null) {
for (XmlAdapter adapter : adapters) {
unmarshaller.setAdapter(adapter);
}
}
}
/*
* Marshalling
*/
public void marshal(Object graph, Result result) throws XmlMappingException {
marshal(graph, result, null);
}
public void marshal(Object graph, Result result, MimeContainer mimeContainer) throws XmlMappingException {
try {
Marshaller marshaller = createMarshaller();
if (mtomEnabled && mimeContainer != null) {
marshaller.setAttachmentMarshaller(new Jaxb2AttachmentMarshaller(mimeContainer));
}
if (TraxUtils.isStaxResult(result)) {
marshalStaxResult(marshaller, graph, result);
}
else {
marshaller.marshal(graph, result);
}
}
catch (JAXBException ex) {
throw convertJaxbException(ex);
}
}
private void marshalStaxResult(Marshaller jaxbMarshaller, Object graph, Result staxResult) throws JAXBException {
XMLStreamWriter streamWriter = TraxUtils.getXMLStreamWriter(staxResult);
if (streamWriter != null) {
jaxbMarshaller.marshal(graph, streamWriter);
}
else {
XMLEventWriter eventWriter = TraxUtils.getXMLEventWriter(staxResult);
if (eventWriter != null) {
jaxbMarshaller.marshal(graph, eventWriter);
}
else {
throw new IllegalArgumentException("StAX Result contains neither XMLStreamWriter nor XMLEventConsumer");
}
}
}
/*
* Unmarshalling
*/
public Object unmarshal(Source source) throws XmlMappingException {
return unmarshal(source, null);
}
public Object unmarshal(Source source, MimeContainer mimeContainer) throws XmlMappingException {
try {
Unmarshaller unmarshaller = createUnmarshaller();
if (mtomEnabled && mimeContainer != null) {
unmarshaller.setAttachmentUnmarshaller(new Jaxb2AttachmentUnmarshaller(mimeContainer));
}
if (TraxUtils.isStaxSource(source)) {
return unmarshalStaxSource(unmarshaller, source);
}
else {
return unmarshaller.unmarshal(source);
}
}
catch (JAXBException ex) {
throw convertJaxbException(ex);
}
}
private Object unmarshalStaxSource(Unmarshaller jaxbUnmarshaller, Source staxSource) throws JAXBException {
XMLStreamReader streamReader = TraxUtils.getXMLStreamReader(staxSource);
if (streamReader != null) {
return jaxbUnmarshaller.unmarshal(streamReader);
}
else {
XMLEventReader eventReader = TraxUtils.getXMLEventReader(staxSource);
if (eventReader != null) {
return jaxbUnmarshaller.unmarshal(eventReader);
}
else {
throw new IllegalArgumentException("StaxSource contains neither XMLStreamReader nor XMLEventReader");
}
}
}
/*
* Inner classes
*/
private static class Jaxb2AttachmentMarshaller extends AttachmentMarshaller {
private final MimeContainer mimeContainer;
private Jaxb2AttachmentMarshaller(MimeContainer mimeContainer) {
this.mimeContainer = mimeContainer;
}
@Override
public String addMtomAttachment(byte[] data,
int offset,
int length,
String mimeType,
String elementNamespace,
String elementLocalName) {
ByteArrayDataSource dataSource = new ByteArrayDataSource(mimeType, data, offset, length);
return addMtomAttachment(new DataHandler(dataSource), elementNamespace, elementLocalName);
}
@Override
public String addMtomAttachment(DataHandler dataHandler, String elementNamespace, String elementLocalName) {
String host = getHost(elementNamespace, dataHandler);
String contentId = UUID.randomUUID() + "@" + host;
mimeContainer.addAttachment("<" + contentId + ">", dataHandler);
try {
contentId = URLEncoder.encode(contentId, "UTF-8");
}
catch (UnsupportedEncodingException e) {
// ignore
}
return "cid:" + contentId;
}
private String getHost(String elementNamespace, DataHandler dataHandler) {
try {
URI uri = new URI(elementNamespace);
return uri.getHost();
}
catch (URISyntaxException e) {
// ignore
}
return dataHandler.getName();
}
@Override
public String addSwaRefAttachment(DataHandler dataHandler) {
String contentId = UUID.randomUUID() + "@" + dataHandler.getName();
mimeContainer.addAttachment(contentId, dataHandler);
return contentId;
}
@Override
public boolean isXOPPackage() {
return mimeContainer.convertToXopPackage();
}
}
private static class Jaxb2AttachmentUnmarshaller extends AttachmentUnmarshaller {
private final MimeContainer mimeContainer;
private Jaxb2AttachmentUnmarshaller(MimeContainer mimeContainer) {
this.mimeContainer = mimeContainer;
}
@Override
public byte[] getAttachmentAsByteArray(String cid) {
try {
DataHandler dataHandler = getAttachmentAsDataHandler(cid);
return FileCopyUtils.copyToByteArray(dataHandler.getInputStream());
}
catch (IOException ex) {
throw new JaxbUnmarshallingFailureException(ex);
}
}
@Override
public DataHandler getAttachmentAsDataHandler(String contentId) {
if (contentId.startsWith("cid:")) {
contentId = contentId.substring("cid:".length());
try {
contentId = URLDecoder.decode(contentId, "UTF-8");
}
catch (UnsupportedEncodingException e) {
// ignore
}
contentId = '<' + contentId + '>';
}
return mimeContainer.getAttachment(contentId);
}
@Override
public boolean isXOPPackage() {
return mimeContainer.isXopPackage();
}
}
/*
* DataSource that wraps around a byte array
*/
private static class ByteArrayDataSource implements DataSource {
private byte[] data;
private String contentType;
private int offset;
private int length;
private ByteArrayDataSource(String contentType, byte[] data, int offset, int length) {
this.contentType = contentType;
this.data = data;
this.offset = offset;
this.length = length;
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(data, offset, length);
}
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException();
}
public String getContentType() {
return contentType;
}
public String getName() {
return "ByteArrayDataSource";
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy