org.apache.axis2.datasource.jaxb.JAXBDSContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of axis2-jaxws Show documentation
Show all versions of axis2-jaxws Show documentation
Axis2 JAXWS Implementation
/*
* 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.axis2.datasource.jaxb;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMException;
import org.apache.axiom.om.XOPEncoded;
import org.apache.axiom.om.impl.MTOMXMLStreamWriter;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.java.security.AccessController;
import org.apache.axis2.jaxws.context.utils.ContextUtils;
import org.apache.axis2.jaxws.message.OccurrenceArray;
import org.apache.axis2.jaxws.message.databinding.JAXBUtils;
import org.apache.axis2.jaxws.message.util.XMLStreamWriterWithOS;
import org.apache.axis2.jaxws.spi.Constants;
import org.apache.axis2.jaxws.utility.JavaUtils;
import org.apache.axis2.jaxws.utility.XMLRootElementUtil;
import org.apache.axis2.jaxws.utility.XmlEnumUtils;
import org.apache.axis2.description.Parameter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.attachment.AttachmentMarshaller;
import javax.xml.bind.attachment.AttachmentUnmarshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.ws.Holder;
import javax.xml.ws.WebServiceException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.security.PrivilegedAction;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
/*
* To marshal or unmarshal a JAXB object, the JAXBContext is necessary.
* In addition, access to the MessageContext and other context objects may be necessary
* to get classloader information, store attachments etc.
*
* The JAXBDSContext bundles all of this information together.
*/
public class JAXBDSContext {
private static final Log log = LogFactory.getLog(JAXBDSContext.class);
public static final boolean DEBUG_ENABLED = log.isDebugEnabled();
private TreeSet contextPackages; // List of packages needed by the context
private String contextPackagesKey; // Unique key that represents the set of packages
private JAXBContext customerJAXBContext; // JAXBContext provided by the customer api
// JAXBContext loaded by the engine. It is weakref'd to allow GC
private WeakReference autoJAXBContext = null;
private JAXBUtils.CONSTRUCTION_TYPE // How the JAXBContext is constructed
constructionType = JAXBUtils.CONSTRUCTION_TYPE.UNKNOWN;
private MessageContext msgContext;
// There are two modes of marshalling and unmarshalling:
// "by java type" and "by schema element".
// The prefered mode is "by schema element" because it is safe and xml-centric.
// However there are some circumstances when "by schema element" is not available.
// Examples: RPC Lit processing (the wire element is defined by a wsdl:part...not schema)
// Doc/Lit Bare "Minimal" Processing (JAXB ObjectFactories are missing...
// and thus we must use "by type" for primitives/String)
// Please don't use "by java type" processing to get around errors.
private Class> processType = null;
private boolean isxmlList =false;
private String webServiceNamespace;
/**
* Full Constructor JAXBDSContext (most performant)
*
* @param packages Set of packages needed by the JAXBContext.
*/
public JAXBDSContext(TreeSet packages, String packagesKey) {
this.contextPackages = packages;
this.contextPackagesKey = packagesKey;
}
/**
* Slightly slower constructor
*
* @param packages
*/
public JAXBDSContext(TreeSet packages) {
this(packages, packages.toString());
}
/**
* Normal Constructor JAXBBlockContext
*
* @param contextPackage
* @deprecated
*/
public JAXBDSContext(String contextPackage) {
this.contextPackages = new TreeSet();
this.contextPackages.add(contextPackage);
this.contextPackagesKey = this.contextPackages.toString();
}
/**
* "Dispatch" Constructor
* Use this full constructor when the JAXBContent is provided by the
* customer.
*
* @param jaxbContext
*/
public JAXBDSContext(JAXBContext jaxbContext) {
this.customerJAXBContext = jaxbContext;
}
/** @return Class representing type of the element */
public TreeSet getContextPackages() {
return contextPackages;
}
public JAXBContext getJAXBContext() throws JAXBException {
return getJAXBContext(null);
}
/**
* @return get the JAXBContext
* @throws JAXBException
*/
public JAXBContext getJAXBContext(ClassLoader cl) throws JAXBException {
return getJAXBContext(cl, false);
}
/**
* @param ClassLoader
* @param forceArrays boolean (if true, then JAXBContext will automatically contain arrays)
* @return get the JAXBContext
* @throws JAXBException
*/
public JAXBContext getJAXBContext(ClassLoader cl, boolean forceArrays) throws JAXBException {
if (customerJAXBContext != null) {
return customerJAXBContext;
}
// Get the weakly cached JAXBContext
JAXBContext jc = null;
if (autoJAXBContext != null) {
jc = autoJAXBContext.get();
}
if (forceArrays &&
jc != null &&
constructionType != JAXBUtils.CONSTRUCTION_TYPE.BY_CLASS_ARRAY_PLUS_ARRAYS) {
if (log.isDebugEnabled()) {
log.debug("A JAXBContext exists but it was not constructed with array class. " +
"The JAXBContext will be rebuilt.");
}
jc = null;
}
if (jc == null) {
if (log.isDebugEnabled()) {
log.debug("Creating a JAXBContext with the context packages.");
}
Holder constructType =
new Holder();
Map properties = null;
/*
* We set the default namespace to the web service namespace to fix an
* obscure bug.
*
* If the class representing a JAXB data object does not define a namespace
* (via an annotation like @XmlType or via ObjectFactory or schema gen information)
* then the namespace information is defaulted.
*
* The xjc tool defaults the namespace information to unqualified.
* However the wsimport tool defaults the namespace to the namespace of the
* webservice.
*
* To "workaround" this issue, a default namespace equal to the webservice
* namespace is set on the JAXB marshaller. This has the effect of changing the
* "unqualified namespaces" into the namespace used by the webservice.
*
*/
if (this.webServiceNamespace != null) {
properties = new HashMap();
properties.put(JAXBUtils.DEFAULT_NAMESPACE_REMAP, this.webServiceNamespace);
}
jc = JAXBUtils.getJAXBContext(contextPackages, constructType, forceArrays,
contextPackagesKey, cl, properties);
constructionType = constructType.value;
autoJAXBContext = new WeakReference(jc);
} else {
if (log.isDebugEnabled()) {
log.debug("Using an existing JAXBContext");
}
}
return jc;
}
public void setWebServiceNamespace(String namespace) {
this.webServiceNamespace = namespace;
}
/** @return RPC Declared Type */
public Class> getProcessType() {
return processType;
}
/**
* The procesess type to indicate the class of the target of the unmarshaling.
* This method should only be used in the cases where the element being unmarshaled
* is not known to the JAXBContext (examples include RPC/Literal processing
* and Doc/Literal Wrapped processing with a non-element wrapper class)
*
* @param type
*/
public void setProcessType(Class> type) {
if (log.isDebugEnabled()) {
log.debug("Process Type set to: " + type);
}
processType = type;
}
public JAXBUtils.CONSTRUCTION_TYPE getConstructionType() {
return constructionType;
}
public boolean isxmlList() {
return isxmlList;
}
public void setIsxmlList(boolean isxmlList) {
if (log.isDebugEnabled()) {
log.debug("isxmlListSet to " + isxmlList);
}
this.isxmlList = isxmlList;
}
public MessageContext getMessageContext() {
return msgContext;
}
public void setMessageContext(MessageContext messageContext) {
this.msgContext = messageContext;
}
public ClassLoader getClassLoader() {
MessageContext context = getMessageContext();
if (context != null) {
Parameter param = context.getParameter(Constants.CACHE_CLASSLOADER);
if (param != null) {
return (ClassLoader) param.getValue();
}
}
return null;
}
protected AttachmentContext createAttachmentContext() {
return new MessageContextAttachmentContext(getMessageContext());
}
/**
* Unmarshal the xml into a JAXB object
* @param element
* @return
* @throws JAXBException
*/
public Object unmarshal(OMElement element) throws JAXBException {
// See the Javadoc of the CustomBuilder interface for a complete explanation of
// the following two instructions:
XOPEncoded xopEncodedStream = element.getXOPEncodedStreamReader(false);
// There may be a preferred classloader that should be used
ClassLoader cl = getClassLoader();
Unmarshaller u = JAXBUtils.getJAXBUnmarshaller(getJAXBContext(cl));
// Create an attachment unmarshaller
AttachmentUnmarshaller aum = new JAXBAttachmentUnmarshaller(createAttachmentContext(), xopEncodedStream.getAttachmentAccessor());
if (aum != null) {
if (DEBUG_ENABLED) {
log.debug("Adding JAXBAttachmentUnmarshaller to Unmarshaller");
}
u.setAttachmentUnmarshaller(aum);
}
Object jaxb = null;
// Unmarshal into the business object.
XMLStreamReader reader = xopEncodedStream.getRootPart();
if (getProcessType() == null) {
jaxb = unmarshalByElement(u, reader); // preferred and always used for
// style=document
} else {
jaxb = unmarshalByType(u,
reader,
getProcessType(),
isxmlList(),
getConstructionType());
}
// Successfully unmarshalled the object
JAXBUtils.releaseJAXBUnmarshaller(getJAXBContext(cl), u);
// Don't close the reader. The reader is owned by the caller, and it
// may contain other xml instance data (other than this JAXB object)
// reader.close();
return jaxb;
}
/**
* Marshal the jaxb object
* @param obj
* @param writer
* @param am AttachmentMarshaller, optional Attachment
*/
public void marshal(Object obj,
XMLStreamWriter writer) throws JAXBException {
if (log.isDebugEnabled()) {
log.debug("enter marshal");
}
// There may be a preferred classloader that should be used
ClassLoader cl = getClassLoader();
// Very easy, use the Context to get the Marshaller.
// Use the marshaller to write the object.
JAXBContext jbc = getJAXBContext(cl);
Marshaller m = JAXBUtils.getJAXBMarshaller(jbc);
if (writer instanceof MTOMXMLStreamWriter && ((MTOMXMLStreamWriter) writer).getOutputFormat() != null) {
String encoding = ((MTOMXMLStreamWriter) writer).getOutputFormat().getCharSetEncoding();
String marshallerEncoding = (String) m.getProperty(Marshaller.JAXB_ENCODING);
// Make sure that the marshaller respects the encoding of the message.
// This is accomplished by setting the encoding on the Marshaller's JAXB_ENCODING property.
if (encoding == null && marshallerEncoding == null) {
if (log.isDebugEnabled()) {
log.debug("The encoding and the marshaller's JAXB_ENCODING are both set to the default (UTF-8)");
}
} else {
// Must set the encoding to an actual String to set it on the Marshaller
if (encoding == null) {
encoding = "UTF-8";
}
if (!encoding.equalsIgnoreCase(marshallerEncoding)) {
if (log.isDebugEnabled()) {
log.debug("The Marshaller.JAXB_ENCODING is " + marshallerEncoding);
log.debug("The Marshaller.JAXB_ENCODING is changed to the message encoding " +
encoding);
}
m.setProperty(Marshaller.JAXB_ENCODING, encoding);
} else {
if (log.isDebugEnabled()) {
log.debug("The encoding and the marshaller's JAXB_ENCODING are both set to:" +
marshallerEncoding);
}
}
}
}
AttachmentMarshaller am = new JAXBAttachmentMarshaller(createAttachmentContext(), writer);
if (am != null) {
if (DEBUG_ENABLED) {
log.debug("Adding JAXBAttachmentMarshaller to Marshaller");
}
m.setAttachmentMarshaller(am);
}
MessageContext mc = getMessageContext();
// If requested install a filter to remove illegal characters
if (writer instanceof MTOMXMLStreamWriter && ContextUtils.isJAXBRemoveIllegalChars(mc)) {
writer = new XMLStreamWriterRemoveIllegalChars((MTOMXMLStreamWriter)writer);
}
// Marshal the object
if (getProcessType() == null) {
marshalByElement(obj,
m,
writer,
true);
//!am.isXOPPackage());
} else {
marshalByType(obj,
m,
writer,
getProcessType(),
isxmlList(),
getConstructionType(),
true); // Attempt to optimize by writing to OutputStream
}
JAXBUtils.releaseJAXBMarshaller(jbc, m);
if (log.isDebugEnabled()) {
log.debug("exit marshal");
}
}
/**
* Preferred way to marshal objects.
*
* @param b Object that can be rendered as an element and the element name is known by the
* Marshaller
* @param m Marshaller
* @param writer XMLStreamWriter
*/
private static void marshalByElement(final Object b, final Marshaller m,
final XMLStreamWriter writer,
final boolean optimize) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
// Marshalling directly to the output stream is faster than marshalling through the
// XMLStreamWriter.
// Take advantage of this optimization if there is an output stream.
try {
OutputStream os = (optimize) ? getOutputStream(writer,m) : null;
if (os != null) {
if (DEBUG_ENABLED) {
log.debug("Invoking marshalByElement. " +
"Marshaling to an OutputStream. " +
"Object is "
+ getDebugName(b));
}
writer.flush();
m.marshal(b, os);
} else {
if (DEBUG_ENABLED) {
log.debug("Invoking marshalByElement. " +
"Marshaling to an XMLStreamWriter. " +
"Object is "
+ getDebugName(b));
}
m.marshal(b, writer);
}
} catch (OMException e) {
throw e;
}
catch (Throwable t) {
throw new OMException(t);
}
return null;
}});
}
/**
* Print out the name of the class of the specified object
* @param o Object
* @return text to use for debugging
*/
private static String getDebugName(Object o) {
String name = (o == null) ? "null" : o.getClass().getCanonicalName();
if (o instanceof JAXBElement) {
name += " containing " + getDebugName(((JAXBElement>) o).getValue());
}
return name;
}
/**
* If the writer is backed by an OutputStream, then return the OutputStream
* @param writer
* @param Marshaller
* @return OutputStream or null
*/
private static OutputStream getOutputStream(XMLStreamWriter writer,
Marshaller m) throws XMLStreamException {
if (log.isDebugEnabled()) {
log.debug("XMLStreamWriter is " + writer);
}
OutputStream os = null;
if (writer instanceof MTOMXMLStreamWriter) {
os = ((MTOMXMLStreamWriter) writer).getOutputStream();
if (log.isDebugEnabled()) {
log.debug("OutputStream accessible from MTOMXMLStreamWriter is " + os);
}
}
if (writer instanceof XMLStreamWriterWithOS) {
os = ((XMLStreamWriterWithOS) writer).getOutputStream();
if (log.isDebugEnabled()) {
log.debug("OutputStream accessible from XMLStreamWriterWithOS is " + os);
}
}
if (os != null) {
String marshallerEncoding = null;
try {
marshallerEncoding = (String) m.getProperty(Marshaller.JAXB_ENCODING);
} catch (PropertyException e) {
if (DEBUG_ENABLED) {
log.debug("Could not query JAXB_ENCODING..Continuing. " + e);
}
}
if (marshallerEncoding != null && !marshallerEncoding.equalsIgnoreCase("UTF-8")) {
if (DEBUG_ENABLED) {
log.debug("Wrapping output stream to remove BOM");
}
os = new BOMOutputStreamFilter(marshallerEncoding, os);
}
}
return os;
}
/**
* The root element being read is defined by schema/JAXB; however its contents are known by
* schema/JAXB. Therefore we use unmarshal by the declared type (This method is used to
* unmarshal rpc elements)
*
* @param u Unmarshaller
* @param reader XMLStreamReader
* @param type Class
* @return Object
* @throws WebServiceException
*/
public static Object unmarshalByType(final Unmarshaller u, final XMLStreamReader reader,
final Class> type, final boolean isList,
final JAXBUtils.CONSTRUCTION_TYPE ctype)
throws WebServiceException {
if (DEBUG_ENABLED) {
log.debug("Invoking unmarshalByType.");
log.debug(" type = " + type);
log.debug(" isList = " + isList);
log.debug(" ctype = "+ ctype);
}
return AccessController.doPrivileged(new PrivilegedAction