/*
 * 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.OMException;
import org.apache.axiom.om.impl.MTOMXMLStreamWriter;
import org.apache.axiom.om.util.XMLStreamWriterRemoveIllegalChars;
import org.apache.axiom.util.stax.XMLStreamReaderUtils;
import org.apache.axiom.util.stax.xop.XOPEncodedStream;
import org.apache.axiom.util.stax.xop.XOPUtils;
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 inputReader
     * @return
     * @throws JAXBException
     */
    public Object unmarshal(XMLStreamReader inputReader) throws JAXBException {

        if (DEBUG_ENABLED) {
            String clsText = (inputReader !=null) ? inputReader.getClass().toString() : "null";
            log.debug("unmarshal with inputReader=" + clsText);
        } 
        // See the Javadoc of the CustomBuilder interface for a complete explanation of
        // the following two instructions:
        XOPEncodedStream xopEncodedStream = XOPUtils.getXOPEncodedStream(inputReader);
        XMLStreamReader reader = XMLStreamReaderUtils.getOriginalXMLStreamReader(xopEncodedStream.getReader());
        if (DEBUG_ENABLED) {
            String clsText = (reader !=null) ? reader.getClass().toString() : "null";
            log.debug("  originalReader=" + clsText);
        } 
        
        // 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.getMimePartProvider());

        if (aum != null) {
            if (DEBUG_ENABLED) {
                log.debug("Adding JAXBAttachmentUnmarshaller to Unmarshaller");
            } 
            u.setAttachmentUnmarshaller(aum);
        }

        Object jaxb = null;

        // Unmarshal into the business object.
        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");
        }
        boolean installedFilter = false;

        try {
            // 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
            installedFilter = installFilter(mc, 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");
            }
        } finally {
            // Make sure the filter is uninstalled
            if (installedFilter) {
                uninstallFilter(writer);
            }
        }
    }
    
    
    /**
     * 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.getClass() == MTOMXMLStreamWriter.class) {
            os = ((MTOMXMLStreamWriter) writer).getOutputStream();
            if (log.isDebugEnabled()) {
                log.debug("OutputStream accessible from MTOMXMLStreamWriter is " + os);
            }
        }
        if (writer.getClass() == XMLStreamWriterWithOS.class) {
            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);
        }
    }
    

    /**
     * Install a JAXB filter if requested
     * @param mc
     * @param writer
     * @return true if filter installed
     */
    private boolean installFilter(MessageContext mc, XMLStreamWriter writer) {
        if (!(writer instanceof MTOMXMLStreamWriter)) {
            return false;
        }
        if (!ContextUtils.isJAXBRemoveIllegalChars(mc)) {
            return false;
        }
        
         
        MTOMXMLStreamWriter mtomWriter = (MTOMXMLStreamWriter) writer;
        mtomWriter.setFilter(new XMLStreamWriterRemoveIllegalChars());
        return true;
    }
    
    /**
     * UninstallInstall a JAXB filter if requested
     * @param mc
     * @param writer
     * @return true if filter installed
     */
    private void uninstallFilter(XMLStreamWriter writer) {
        MTOMXMLStreamWriter mtomWriter = (MTOMXMLStreamWriter) writer;
        mtomWriter.removeFilter();
    }

}
