Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.camel.converter.jaxb.JaxbDataFormat Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.camel.converter.jaxb;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.JAXBIntrospector;
import jakarta.xml.bind.MarshalException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.PropertyException;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.ValidationEvent;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.xml.sax.SAXException;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePropertyKey;
import org.apache.camel.InvalidPayloadException;
import org.apache.camel.NoTypeConversionAvailableException;
import org.apache.camel.TypeConverter;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.spi.DataFormatContentTypeHeader;
import org.apache.camel.spi.DataFormatName;
import org.apache.camel.spi.annotations.Dataformat;
import org.apache.camel.support.ResourceHelper;
import org.apache.camel.support.service.ServiceSupport;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A data format ({@link DataFormat}) using JAXB2 to marshal to
* and from XML
*/
@Dataformat("jaxb")
public class JaxbDataFormat extends ServiceSupport
implements DataFormat, DataFormatName, DataFormatContentTypeHeader, CamelContextAware {
private static final Logger LOG = LoggerFactory.getLogger(JaxbDataFormat.class);
private SchemaFactory schemaFactory;
private CamelContext camelContext;
private JAXBContext context;
private JAXBIntrospector introspector;
private String contextPath;
private boolean contextPathIsClassName;
private String schema;
private int schemaSeverityLevel; // 0 = warning, 1 = error, 2 = fatal
private String schemaLocation;
private String noNamespaceSchemaLocation;
private boolean prettyPrint = true;
private boolean objectFactory = true;
private boolean ignoreJAXBElement = true;
private boolean mustBeJAXBElement;
private boolean filterNonXmlChars;
private String encoding;
private boolean fragment;
// partial support
private QName partNamespace;
private Class> partClass;
private Map namespacePrefix;
private JaxbNamespacePrefixMapper namespacePrefixMapper;
private JaxbXmlStreamWriterWrapper xmlStreamWriterWrapper;
private TypeConverter typeConverter;
private Schema cachedSchema;
private Map jaxbProviderProperties;
private boolean contentTypeHeader = true;
private String accessExternalSchemaProtocols;
public JaxbDataFormat() {
}
public JaxbDataFormat(JAXBContext context) {
this.context = context;
}
public JaxbDataFormat(String contextPath) {
this.contextPath = contextPath;
}
@Override
public String getDataFormatName() {
return "jaxb";
}
@Override
public void marshal(Exchange exchange, Object graph, OutputStream stream) throws IOException {
try {
// must create a new instance of marshaller as its not thread safe
Marshaller marshaller = createMarshaller();
if (isPrettyPrint()) {
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
}
// exchange take precedence over encoding option
String charset = exchange.getProperty(ExchangePropertyKey.CHARSET_NAME, String.class);
if (charset == null) {
charset = encoding;
//Propagate the encoding of the exchange
if (charset != null) {
exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, charset);
}
}
if (charset != null) {
marshaller.setProperty(Marshaller.JAXB_ENCODING, charset);
}
if (isFragment()) {
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
}
if (ObjectHelper.isNotEmpty(schemaLocation)) {
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation);
}
if (ObjectHelper.isNotEmpty(noNamespaceSchemaLocation)) {
marshaller.setProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, noNamespaceSchemaLocation);
}
if (namespacePrefixMapper != null) {
marshaller.setProperty(namespacePrefixMapper.getRegistrationKey(), namespacePrefixMapper);
}
// Inject any JAX-RI custom properties from the exchange or from the instance into the marshaller
injectCustomProperties(exchange, marshaller);
doMarshal(exchange, graph, stream, marshaller, charset);
if (contentTypeHeader) {
exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "application/xml");
}
} catch (Exception e) {
throw new IOException(e);
}
}
private void injectCustomProperties(Exchange exchange, Marshaller marshaller) throws PropertyException {
Map customProperties = exchange.getProperty(JaxbConstants.JAXB_PROVIDER_PROPERTIES, Map.class);
if (customProperties == null) {
customProperties = getJaxbProviderProperties();
}
if (customProperties != null) {
for (Entry property : customProperties.entrySet()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Using JAXB Provider Property {}={}", property.getKey(), property.getValue());
}
marshaller.setProperty(property.getKey(), property.getValue());
}
}
}
void doMarshal(Exchange exchange, Object graph, OutputStream stream, Marshaller marshaller, String charset)
throws Exception {
Object element = graph;
QName partNamespaceOnDataFormat = getPartNamespace();
String partClassFromHeader = exchange.getIn().getHeader(JaxbConstants.JAXB_PART_CLASS, String.class);
String partNamespaceFromHeader = exchange.getIn().getHeader(JaxbConstants.JAXB_PART_NAMESPACE, String.class);
if ((partClass != null || partClassFromHeader != null)
&& (partNamespaceOnDataFormat != null || partNamespaceFromHeader != null)) {
element = toElement(graph, partClassFromHeader, partNamespaceFromHeader, partNamespaceOnDataFormat);
}
// only marshal if its possible
if (introspector.isElement(element)) {
tryMarshal(exchange, stream, marshaller, charset, element);
return;
} else if (objectFactory && element != null) {
if (tryFromFactory(exchange, stream, marshaller, charset, element)) {
return;
}
}
// cannot marshal
if (!mustBeJAXBElement) {
// write the graph as is to the output stream
writeGraph(exchange, graph, stream);
} else {
throw new InvalidPayloadException(exchange, JAXBElement.class);
}
}
private boolean tryFromFactory(
Exchange exchange, OutputStream stream, Marshaller marshaller, String charset, Object element)
throws IllegalAccessException, InvocationTargetException, JAXBException, InstantiationException {
Method objectFactoryMethod = JaxbHelper.getJaxbElementFactoryMethod(camelContext, element.getClass());
if (objectFactoryMethod != null) {
try {
tryMarshallFromInstance(exchange, stream, marshaller, charset, objectFactoryMethod, element);
return true;
} catch (Exception e) {
// if a schema is set then an MarshallException is thrown when the XML is not valid
// and the method must throw this exception as it would when the object in the body is a root element
// or a partial class (the other alternatives above)
//
// it would be best to completely remove the exception handler here but it's left for backwards compatibility reasons.
if (MarshalException.class.isAssignableFrom(e.getClass()) && schema != null) {
throw e;
}
LOG.debug("Unable to create JAXBElement object for type {} due to {}", element.getClass(),
e.getMessage(), e);
}
}
return false;
}
private static void writeGraph(Exchange exchange, Object graph, OutputStream stream)
throws NoTypeConversionAvailableException, IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Attempt to marshalling non JAXBElement with type {} as InputStream",
ObjectHelper.classCanonicalName(graph));
}
InputStream is = exchange.getContext().getTypeConverter().mandatoryConvertTo(InputStream.class, exchange, graph);
IOHelper.copyAndCloseInput(is, stream);
}
private void tryMarshallFromInstance(
Exchange exchange, OutputStream stream, Marshaller marshaller, String charset, Method objectFactoryMethod,
Object element)
throws IllegalAccessException, InvocationTargetException, JAXBException, InstantiationException {
Object instance = objectFactoryMethod.getDeclaringClass().newInstance();
Object toMarshall = objectFactoryMethod.invoke(instance, element);
if (asXmlStreamWriter(exchange)) {
XMLStreamWriter writer = typeConverter.convertTo(XMLStreamWriter.class, exchange, stream);
if (needFiltering(exchange)) {
writer = new FilteringXmlStreamWriter(writer, charset);
}
if (xmlStreamWriterWrapper != null) {
writer = xmlStreamWriterWrapper.wrapWriter(writer);
}
marshaller.marshal(toMarshall, writer);
} else {
marshaller.marshal(toMarshall, stream);
}
return;
}
private void tryMarshal(Exchange exchange, OutputStream stream, Marshaller marshaller, String charset, Object element)
throws JAXBException {
if (asXmlStreamWriter(exchange)) {
XMLStreamWriter writer = typeConverter.convertTo(XMLStreamWriter.class, exchange, stream);
if (needFiltering(exchange)) {
writer = new FilteringXmlStreamWriter(writer, charset);
}
if (xmlStreamWriterWrapper != null) {
writer = xmlStreamWriterWrapper.wrapWriter(writer);
}
marshaller.marshal(element, writer);
} else {
marshaller.marshal(element, stream);
}
}
private Object toElement(
Object graph, String partClassFromHeader, String partNamespaceFromHeader, QName partNamespaceOnDataFormat)
throws JAXBException {
if (partClassFromHeader != null) {
try {
partClass = camelContext.getClassResolver().resolveMandatoryClass(partClassFromHeader, Object.class);
} catch (ClassNotFoundException e) {
throw new JAXBException(e);
}
}
if (partNamespaceFromHeader != null) {
partNamespaceOnDataFormat = QName.valueOf(partNamespaceFromHeader);
}
return new JAXBElement<>(partNamespaceOnDataFormat, (Class) partClass, graph);
}
private boolean asXmlStreamWriter(Exchange exchange) {
return needFiltering(exchange) || xmlStreamWriterWrapper != null;
}
@Override
public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
return unmarshal(exchange, (Object) stream);
}
@Override
public Object unmarshal(Exchange exchange, Object body) throws Exception {
try {
Object answer;
XMLStreamReader xmlReader;
if (needFiltering(exchange)) {
xmlReader
= typeConverter.convertTo(XMLStreamReader.class, exchange, createNonXmlFilterReader(exchange, body));
} else {
xmlReader = typeConverter.tryConvertTo(XMLStreamReader.class, exchange, body);
if (xmlReader == null) {
// fallback to input stream
InputStream is = getCamelContext().getTypeConverter().mandatoryConvertTo(InputStream.class, exchange, body);
xmlReader = typeConverter.convertTo(XMLStreamReader.class, exchange, is);
}
}
String partClassFromHeader = exchange.getIn().getHeader(JaxbConstants.JAXB_PART_CLASS, String.class);
if (partClass != null || partClassFromHeader != null) {
// partial unmarshalling
if (partClassFromHeader != null) {
try {
partClass = camelContext.getClassResolver().resolveMandatoryClass(partClassFromHeader, Object.class);
} catch (ClassNotFoundException e) {
throw new JAXBException(e);
}
}
answer = createUnmarshaller().unmarshal(xmlReader, partClass);
} else {
answer = createUnmarshaller().unmarshal(xmlReader);
}
if (answer instanceof JAXBElement && isIgnoreJAXBElement()) {
answer = ((JAXBElement>) answer).getValue();
}
return answer;
} catch (JAXBException e) {
throw new IOException(e);
}
}
private NonXmlFilterReader createNonXmlFilterReader(Exchange exchange, Object body)
throws NoTypeConversionAvailableException {
Reader reader = getCamelContext().getTypeConverter().tryConvertTo(Reader.class, exchange, body);
if (reader == null) {
// fallback to input stream
InputStream is = getCamelContext().getTypeConverter().mandatoryConvertTo(InputStream.class, exchange, body);
reader = new InputStreamReader(is);
}
return new NonXmlFilterReader(reader);
}
protected boolean needFiltering(Exchange exchange) {
// exchange property takes precedence over data format property
return exchange == null
? filterNonXmlChars : exchange.getProperty(Exchange.FILTER_NON_XML_CHARS, filterNonXmlChars, Boolean.class);
}
// Properties
// -------------------------------------------------------------------------
public boolean isIgnoreJAXBElement() {
return ignoreJAXBElement;
}
public void setIgnoreJAXBElement(boolean flag) {
ignoreJAXBElement = flag;
}
public boolean isMustBeJAXBElement() {
return mustBeJAXBElement;
}
public void setMustBeJAXBElement(boolean mustBeJAXBElement) {
this.mustBeJAXBElement = mustBeJAXBElement;
}
public JAXBContext getContext() {
return context;
}
public void setContext(JAXBContext context) {
this.context = context;
}
public String getContextPath() {
return contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public boolean isContextPathIsClassName() {
return contextPathIsClassName;
}
public void setContextPathIsClassName(boolean contextPathIsClassName) {
this.contextPathIsClassName = contextPathIsClassName;
}
public SchemaFactory getSchemaFactory() throws SAXException {
return schemaFactory;
}
public void setSchemaFactory(SchemaFactory schemaFactory) {
this.schemaFactory = schemaFactory;
}
public String getSchema() {
return schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public int getSchemaSeverityLevel() {
return schemaSeverityLevel;
}
public void setSchemaSeverityLevel(int schemaSeverityLevel) {
this.schemaSeverityLevel = schemaSeverityLevel;
}
public boolean isPrettyPrint() {
return prettyPrint;
}
public void setPrettyPrint(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
}
public boolean isObjectFactory() {
return objectFactory;
}
public void setObjectFactory(boolean objectFactory) {
this.objectFactory = objectFactory;
}
public boolean isFragment() {
return fragment;
}
public void setFragment(boolean fragment) {
this.fragment = fragment;
}
public boolean isFilterNonXmlChars() {
return filterNonXmlChars;
}
public void setFilterNonXmlChars(boolean filterNonXmlChars) {
this.filterNonXmlChars = filterNonXmlChars;
}
public String getEncoding() {
return encoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public QName getPartNamespace() {
return partNamespace;
}
public void setPartNamespace(QName partNamespace) {
this.partNamespace = partNamespace;
}
public Class> getPartClass() {
return partClass;
}
public void setPartClass(Class> partClass) {
this.partClass = partClass;
}
public Map getNamespacePrefix() {
return namespacePrefix;
}
public void setNamespacePrefix(Map namespacePrefix) {
this.namespacePrefix = namespacePrefix;
}
@Override
public CamelContext getCamelContext() {
return camelContext;
}
@Override
public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
}
public JaxbXmlStreamWriterWrapper getXmlStreamWriterWrapper() {
return xmlStreamWriterWrapper;
}
public void setXmlStreamWriterWrapper(JaxbXmlStreamWriterWrapper xmlStreamWriterWrapper) {
this.xmlStreamWriterWrapper = xmlStreamWriterWrapper;
}
public String getSchemaLocation() {
return schemaLocation;
}
public void setSchemaLocation(String schemaLocation) {
this.schemaLocation = schemaLocation;
}
public String getNoNamespaceSchemaLocation() {
return noNamespaceSchemaLocation;
}
public void setNoNamespaceSchemaLocation(String schemaLocation) {
this.noNamespaceSchemaLocation = schemaLocation;
}
public Map getJaxbProviderProperties() {
return jaxbProviderProperties;
}
public void setJaxbProviderProperties(Map jaxbProviderProperties) {
this.jaxbProviderProperties = jaxbProviderProperties;
}
public boolean isContentTypeHeader() {
return contentTypeHeader;
}
/**
* If enabled then JAXB will set the Content-Type header to application/xml when marshalling.
*/
public void setContentTypeHeader(boolean contentTypeHeader) {
this.contentTypeHeader = contentTypeHeader;
}
public String getAccessExternalSchemaProtocols() {
return accessExternalSchemaProtocols;
}
public void setAccessExternalSchemaProtocols(String accessExternalSchemaProtocols) {
this.accessExternalSchemaProtocols = accessExternalSchemaProtocols;
}
@Override
@SuppressWarnings("unchecked")
protected void doStart() throws Exception {
ObjectHelper.notNull(camelContext, "CamelContext");
if (context == null) {
// if context not injected, create one and resolve partial class up front so they are ready to be used
context = createContext();
}
introspector = context.createJAXBIntrospector();
if (namespacePrefix != null) {
namespacePrefixMapper = NamespacePrefixMapperFactory.newNamespacePrefixMapper(camelContext, namespacePrefix);
}
typeConverter = camelContext.getTypeConverter();
if (schema != null) {
cachedSchema = createSchema(getSources());
}
LOG.debug("JaxbDataFormat [prettyPrint={}, objectFactory={}]", prettyPrint, objectFactory);
}
@Override
protected void doStop() throws Exception {
}
/**
* Strategy to create JAXB context
*/
protected JAXBContext createContext() throws Exception {
if (contextPath != null) {
// prefer to use application class loader which is most likely to be able to
// load the class which has been JAXB annotated
ClassLoader cl = camelContext.getApplicationContextClassLoader();
if (cl != null) {
if (contextPathIsClassName) {
LOG.debug("Creating JAXBContext with className: {} and ApplicationContextClassLoader: {}",
contextPath, cl);
Class clazz = camelContext.getClassResolver().resolveMandatoryClass(contextPath, cl);
return JAXBContext.newInstance(clazz);
} else {
LOG.debug("Creating JAXBContext with contextPath: {} and ApplicationContextClassLoader: {}",
contextPath, cl);
return JAXBContext.newInstance(contextPath, cl);
}
} else {
if (contextPathIsClassName) {
LOG.debug("Creating JAXBContext with className: {}", contextPath);
Class clazz = camelContext.getClassResolver().resolveMandatoryClass(contextPath);
return JAXBContext.newInstance(clazz);
} else {
LOG.debug("Creating JAXBContext with contextPath: {}", contextPath);
return JAXBContext.newInstance(contextPath);
}
}
} else {
LOG.debug("Creating JAXBContext");
return JAXBContext.newInstance();
}
}
protected Unmarshaller createUnmarshaller() throws JAXBException {
Unmarshaller unmarshaller = getContext().createUnmarshaller();
if (schema != null) {
unmarshaller.setSchema(cachedSchema);
unmarshaller.setEventHandler((ValidationEvent event) -> {
// continue if the severity is lower than the configured level
return event.getSeverity() < getSchemaSeverityLevel();
});
}
return unmarshaller;
}
protected Marshaller createMarshaller() throws JAXBException {
Marshaller marshaller = getContext().createMarshaller();
if (schema != null) {
marshaller.setSchema(cachedSchema);
marshaller.setEventHandler((ValidationEvent event) -> {
// continue if the severity is lower than the configured level
return event.getSeverity() < getSchemaSeverityLevel();
});
}
return marshaller;
}
private Schema createSchema(Source[] sources) throws SAXException {
SchemaFactory factory = createSchemaFactory(accessExternalSchemaProtocols);
return factory.newSchema(sources);
}
private Source[] getSources() throws FileNotFoundException, MalformedURLException {
// we support multiple schema by delimiting by comma
String[] schemas = schema.split(",");
Source[] sources = new Source[schemas.length];
for (int i = 0; i < schemas.length; i++) {
URL schemaUrl = ResourceHelper.resolveMandatoryResourceAsUrl(camelContext, schemas[i]);
sources[i] = new StreamSource(schemaUrl.toExternalForm());
}
return sources;
}
private static SchemaFactory createSchemaFactory(String protocols) throws SAXException {
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
if (protocols == null || "false".equals(protocols) || "none".equals(protocols)) {
protocols = "";
LOG.debug("Configuring SchemaFactory to not allow access to external DTD/Schema");
} else {
LOG.debug("Configuring SchemaFactory to allow access to external DTD/Schema using protocols: {}", protocols);
}
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, protocols);
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, protocols);
return factory;
}
}