blob: 5faa061280b46ca62879f687f2e5bc8b877a19c5 [file] [log] [blame]
/*
* 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<String> 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<JAXBContext> 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<String> packages, String packagesKey) {
this.contextPackages = packages;
this.contextPackagesKey = packagesKey;
}
/**
* Slightly slower constructor
*
* @param packages
*/
public JAXBDSContext(TreeSet<String> packages) {
this(packages, packages.toString());
}
/**
* Normal Constructor JAXBBlockContext
*
* @param contextPackage
* @deprecated
*/
public JAXBDSContext(String contextPackage) {
this.contextPackages = new TreeSet<String>();
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<String> 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<JAXBUtils.CONSTRUCTION_TYPE> constructType =
new Holder<JAXBUtils.CONSTRUCTION_TYPE>();
Map<String, Object> 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<String, Object>();
properties.put(JAXBUtils.DEFAULT_NAMESPACE_REMAP, this.webServiceNamespace);
}
jc = JAXBUtils.getJAXBContext(contextPackages, constructType, forceArrays,
contextPackagesKey, cl, properties);
constructionType = constructType.value;
autoJAXBContext = new WeakReference<JAXBContext>(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<XMLStreamReader> 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<Void>() {
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<Object>() {
public Object run() {
try {
// Unfortunately RPC is type based. Thus a
// declared type must be used to unmarshal the xml.
Object jaxb;
if (!isList) {
// case: We are not unmarshalling an xsd:list but an Array.
if (type.isArray()) {
// If the context is created using package
// we will not have common arrays or type array in the context
// but there is not much we can do about it so seralize it as
// usual
if (ctype == JAXBUtils.CONSTRUCTION_TYPE.BY_CONTEXT_PATH) {
if (DEBUG_ENABLED) {
log.debug("Unmarshal Array via BY_CONTEXT_PATH approach");
}
jaxb = u.unmarshal(reader, type);
}
// list on client array on server, Can happen only in start from java
// case.
else if ((ctype == JAXBUtils.CONSTRUCTION_TYPE.BY_CLASS_ARRAY)) {
// The type could be any Object or primitive
//process primitives first
//first verify if we have a primitive type associated in the array.
//array could be single dimension or multi dimension.
Class<?> cType = type.getComponentType();
while(cType.isArray()){
cType = cType.getComponentType();
}
if(cType.isPrimitive()){
if (DEBUG_ENABLED) {
log.debug("Unmarshal Array of primitive via BY_CLASS_ARRAY approach");
}
jaxb = u.unmarshal(reader, type);
}
// process non primitive
// I will first unmarshall the xmldata to a String[]
// Then use the unmarshalled jaxbElement to create
// proper type Object Array.
else{
if (DEBUG_ENABLED) {
log.debug("Unmarshal Array of non-primitive via BY_CLASS_ARRAY approach");
}
jaxb = unmarshalArray(reader, u, type);
}
} else {
if (DEBUG_ENABLED) {
log.debug("Unmarshal Array");
}
jaxb = u.unmarshal(reader, type);
}
} else if (type.isEnum()) {
// When JAXBContext is created using a context path, it will not
// include Enum classes.
// These classes have @XmlEnum annotation but not @XmlType/@XmlElement,
// so the user will see MarshallingEx, class not known to ctxt.
//
// This is a jax-b defect, for now this fix is in place to pass CTS.
// This only fixes the
// situation where the enum is the top-level object (e.g., message-part
// in rpc-lit scenario)
//
// Sample of what enum looks like:
// @XmlEnum public enum EnumString {
// @XmlEnumValue("String1") STRING_1("String1"),
// @XmlEnumValue("String2") STRING_2("String2");
//
// public static getValue(String){} <-- resolves a "value" to an emum
// object
// ... }
if (DEBUG_ENABLED) {
log.debug("Unmarshalling " + type.getName()
+ " as Enum");
}
JAXBElement<String> enumValue = u.unmarshal(reader, XmlEnumUtils.getConversionType(type));
if (enumValue != null) {
jaxb = XmlEnumUtils.fromValue(type, enumValue.getValue());
} else {
jaxb = null;
}
}
//Normal case: We are not unmarshalling a xsd:list or Array
else {
if (DEBUG_ENABLED) {
log.debug("Unmarshalling normal case (not array, not xsd:list, not enum)");
}
jaxb = u.unmarshal(reader, type);
}
} else {
// If this is an xsd:list, we need to return the appropriate
// list or array (see NOTE above)
// First unmarshal as a String
//Second convert the String into a list or array
if (DEBUG_ENABLED) {
log.debug("Unmarshalling xsd:list");
}
jaxb = unmarshalAsListOrArray(reader, u, type);
}
if (log.isDebugEnabled()) {
if (jaxb == null) {
if (DEBUG_ENABLED) {
log.debug("End unmarshalByType returning null object");
}
} else if (jaxb instanceof JAXBElement) {
JAXBElement<?> jbe = (JAXBElement<?>) jaxb;
if (DEBUG_ENABLED) {
log.debug("End unmarshalByType returning JAXBElement");
log.debug(" Class = " + jbe.getDeclaredType());
log.debug(" QName = " + jbe.getName());
}
} else {
if (DEBUG_ENABLED) {
log.debug("End unmarshalByType returning " + jaxb.getClass());
}
}
}
return jaxb;
} catch (OMException e) {
throw e;
} catch (Throwable t) {
throw new OMException(t);
}
}
});
}
private static Object unmarshalArray(final XMLStreamReader reader,
final Unmarshaller u,
Class<?> type)
throws Exception {
try {
if (DEBUG_ENABLED) {
log.debug("Invoking unmarshalArray");
}
Object jaxb = AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
return u.unmarshal(reader, String[].class);
} catch (OMException e) {
throw e;
} catch (Throwable t) {
throw new OMException(t);
}
}
});
Object typeObj = getTypeEnabledObject(jaxb);
// Now convert String Array in to the required Type Array.
if (typeObj instanceof String[]) {
String[] strArray = (String[]) typeObj;
Object obj = XSDListUtils.fromStringArray(strArray, type);
QName qName =
XMLRootElementUtil.getXmlRootElementQNameFromObject(jaxb);
jaxb = new JAXBElement(qName, type, obj);
}
return jaxb;
} catch (OMException e) {
throw e;
} catch (Throwable t) {
throw new OMException(t);
}
}
/**
* convert the String into a list or array
* @param <T>
* @param jaxb
* @param type
* @return
* @throws IllegalAccessException
* @throws ParseException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws DatatypeConfigurationException
* @throws InvocationTargetException
*/
public static Object unmarshalAsListOrArray(final XMLStreamReader reader,
final Unmarshaller u,
Class<?> type)
throws IllegalAccessException, ParseException,NoSuchMethodException,
InstantiationException,
DatatypeConfigurationException,InvocationTargetException,JAXBException {
if (DEBUG_ENABLED) {
log.debug("Invoking unmarshalAsListOrArray");
}
// If this is an xsd:list, we need to return the appropriate
// list or array (see NOTE above)
// First unmarshal as a String
Object jaxb = null;
try {
jaxb = AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
return u.unmarshal(reader, String.class);
} catch (OMException e) {
throw e;
} catch (Throwable t) {
throw new OMException(t);
}
}
});
} catch (OMException e) {
throw e;
} catch (Throwable t) {
throw new OMException(t);
}
//Second convert the String into a list or array
if (getTypeEnabledObject(jaxb) instanceof String) {
QName qName = XMLRootElementUtil.getXmlRootElementQNameFromObject(jaxb);
Object obj = XSDListUtils.fromXSDListString((String) getTypeEnabledObject(jaxb), type);
return new JAXBElement(qName, type, obj);
} else {
return jaxb;
}
}
/**
* Return type enabled object
*
* @param obj type or element enabled object
* @return type enabled object
*/
static Object getTypeEnabledObject(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof JAXBElement) {
return ((JAXBElement<?>) obj).getValue();
}
return obj;
}
private static boolean isOccurrenceArray(Object obj) {
return (obj instanceof JAXBElement) &&
(((JAXBElement<?>)obj).getValue() instanceof OccurrenceArray);
}
/**
* Marshal objects by type
*
* @param b Object that can be rendered as an element, but the element name is not known to the
* schema (i.e. rpc)
* @param m Marshaller
* @param writer XMLStreamWriter
* @param type Class
* @param isList true if this is an XmlList
* @param ctype CONSTRUCTION_TYPE
* @param optimize boolean set to true if optimization directly to
* outputstream should be attempted.
*/
private void marshalByType(final Object b, final Marshaller m,
final XMLStreamWriter writer, final Class<?> type,
final boolean isList,
final JAXBUtils.CONSTRUCTION_TYPE ctype,
final boolean optimize)
throws WebServiceException {
if (log.isDebugEnabled()) {
log.debug("Enter marshalByType b=" + getDebugName(b) +
" type=" + type +
" marshaller=" + m +
" writer=" + writer +
" isList=" + isList +
" ctype=" + ctype +
" optimize=" + optimize);
}
if (isOccurrenceArray(b)) {
marshalOccurrenceArray((JAXBElement<?>) b, m, writer);
return;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
// NOTE
// Example:
// <xsd:simpleType name="LongList">
// <xsd:list>
// <xsd:simpleType>
// <xsd:restriction base="xsd:unsignedInt"/>
// </xsd:simpleType>
// </xsd:list>
// </xsd:simpleType>
// <element name="myLong" nillable="true" type="impl:LongList"/>
//
// LongList will be represented as an int[]
// On the wire myLong will be represented as a list of integers
// with intervening whitespace
// <myLong>1 2 3</myLong>
//
// Unfortunately, we are trying to marshal by type. Therefore
// we want to marshal an element (foo) that is unknown to schema.
// If we use the normal marshal code, the wire will look like
// this (which is incorrect):
// <foo><item>1</item><item>2</item><item>3</item></foo>
//
// The solution is to detect this situation and marshal the
// String instead. Then we get the correct wire format:
// <foo>1 2 3</foo>
Object jbo = b;
if(DEBUG_ENABLED){
log.debug("check if marshalling list or array object, type = "+ (( b!=null )? b.getClass().getName():"null"));
}
if (isList) {
if (DEBUG_ENABLED) {
log.debug("marshalling type which is a List");
}
// This code assumes that the JAXBContext does not understand
// the array or list. In such cases, the contents are converted
// to a String and passed directly.
if (ctype == JAXBUtils.CONSTRUCTION_TYPE.BY_CONTEXT_PATH) {
QName qName = XMLRootElementUtil.getXmlRootElementQNameFromObject(b);
String text = XSDListUtils.toXSDListString(getTypeEnabledObject(b));
if (DEBUG_ENABLED) {
log.debug("marshalling [context path approach] " +
"with xmllist text = " + text);
}
jbo = new JAXBElement<String>(qName, String.class, text);
} else if (ctype == JAXBUtils.CONSTRUCTION_TYPE.BY_CLASS_ARRAY) {
// Some versions of JAXB have array/list processing built in.
// This code is a safeguard because apparently some versions
// of JAXB don't.
QName qName = XMLRootElementUtil.getXmlRootElementQNameFromObject(b);
String text = XSDListUtils.toXSDListString(getTypeEnabledObject(b));
if (DEBUG_ENABLED) {
log.debug("marshalling [class array approach] " +
"with xmllist text = " + text);
}
jbo = new JAXBElement<String>(qName, String.class, text);
}
}
// When JAXBContext is created using a context path, it will not include Enum
// classes.
// These classes have @XmlEnum annotation but not @XmlType/@XmlElement, so the
// user will see MarshallingEx, class not known to ctxt.
//
// This is a jax-b defect, for now this fix is in place to pass CTS. This only
// fixes the
// situation where the enum is the top-level object (e.g., message-part in
// rpc-lit scenario)
//
// Sample of what enum looks like:
// @XmlEnum public enum EnumString {
// @XmlEnumValue("String1") STRING_1("String1"),
// @XmlEnumValue("String2") STRING_2("String2");
// ... }
if (type.isEnum()) {
if (b != null) {
if (DEBUG_ENABLED) {
log.debug("marshalByType. Marshaling " + type.getName()
+ " as Enum");
}
JAXBElement<?> jbe = (JAXBElement<?>) b;
String value = XMLRootElementUtil.getEnumValue((Enum<?>) jbe.getValue());
jbo = new JAXBElement<String>(jbe.getName(), String.class, value);
}
}
// If the output stream is available, marshal directly to it
OutputStream os = (optimize) ? getOutputStream(writer, m) : null;
if (os == null){
if (DEBUG_ENABLED) {
log.debug("Invoking marshalByType. " +
"Marshaling to an XMLStreamWriter. Object is "
+ getDebugName(jbo));
}
m.marshal(jbo, writer);
} else {
if (DEBUG_ENABLED) {
log.debug("Invoking marshalByType. " +
"Marshaling to an OutputStream. Object is "
+ getDebugName(jbo));
}
m.marshal(jbo, os);
}
} catch (OMException e) {
throw e;
} catch (Throwable t) {
throw new OMException(t);
}
return null;
}
});
}
/**
* Marshal array objects by type
*
* Invoke marshalByType for each element in the array
*
* @param jaxb_in JAXBElement containing a value that is a List or array
* @param m_in Marshaller
* @param writer_in XMLStreamWriter
*/
private void marshalOccurrenceArray(
final JAXBElement<?> jbe_in,
final Marshaller m_in,
final XMLStreamWriter writer_in) {
if (log.isDebugEnabled()) {
log.debug("Enter marshalOccurrenceArray");
log.debug(" Marshaller = " + JavaUtils.getObjectIdentity(m_in));
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
Marshaller m = m_in;
JAXBContext newJBC = null;
if (getConstructionType() != JAXBUtils.CONSTRUCTION_TYPE.BY_CLASS_ARRAY_PLUS_ARRAYS) {
// Rebuild JAXBContext
// There may be a preferred classloader that should be used
if (log.isDebugEnabled()) {
log.debug("Building a JAXBContext with array capability");
}
ClassLoader cl = getClassLoader();
newJBC = getJAXBContext(cl, true);
m = JAXBUtils.getJAXBMarshaller(newJBC);
if (log.isDebugEnabled()) {
log.debug("The new JAXBContext was constructed with " + getConstructionType());
}
}
OccurrenceArray occurArray = (OccurrenceArray) jbe_in.getValue();
// Create a new JAXBElement.
// The name is the name of the individual occurence elements
// Type type is Object[]
// The value is the array of Object[] representing each element
JAXBElement<Object[]> jbe = new JAXBElement<Object[]>(jbe_in.getName(),
Object[].class,
occurArray.getAsArray());
// The jaxb marshal command cannot write out a list/array
// of occurence elements. So we marshal it as a single
// element containing items...and then put a filter on the
// writer to transform it into a stream of occurence elements
XMLStreamWriterArrayFilter writer = new XMLStreamWriterArrayFilter(writer_in);
m.marshal(jbe, writer);
if (newJBC != null) {
JAXBUtils.releaseJAXBMarshaller(newJBC, m);
}
return null;
} catch (OMException e) {
throw e;
} catch (Throwable t) {
throw new OMException(t);
}
}
});
if (log.isDebugEnabled()) {
log.debug("Exit marshalOccurrenceArray");
}
}
/**
* Preferred way to unmarshal objects
*
* @param u Unmarshaller
* @param reader XMLStreamReader
* @return Object that represents an element
* @throws WebServiceException
*/
public static Object unmarshalByElement(final Unmarshaller u, final XMLStreamReader reader)
throws WebServiceException {
try {
if (DEBUG_ENABLED) {
log.debug("Invoking unMarshalByElement");
}
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
return u.unmarshal(reader);
} catch (OMException e) {
throw e;
} catch (Throwable t) {
throw new OMException(t);
}
}
});
} catch (OMException e) {
throw e;
} catch (Throwable t) {
throw new OMException(t);
}
}
}