/*
 * 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.axis2.context.MessageContext;
import org.apache.axis2.java.security.AccessController;
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.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.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.lang.reflect.Method;
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();
        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 {
        if (customerJAXBContext != null) {
            return customerJAXBContext;
        }
        
        // Get the weakly cached JAXBContext
        JAXBContext jc = null;
        if (autoJAXBContext != null) {
            jc = autoJAXBContext.get();
        }
        
        if (jc == null) {
            if (log.isDebugEnabled()) {
                log.debug(
                        "A JAXBContext did not exist, creating a new one 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
             * obscur 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, 
                                          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;
    }

    /**
     * Set RPC Declared Type.  The use of use this property if the message is style=document is
     * discouraged.
     *
     * @param type
     */
    public void setProcessType(Class type) {
        processType = type;
    }

    public JAXBUtils.CONSTRUCTION_TYPE getConstructionType() {
        return constructionType;
    }

    public boolean isxmlList() {
        return isxmlList;
    }

    public void setIsxmlList(boolean 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) {
            return (ClassLoader) context.getProperty(Constants.CACHE_CLASSLOADER);
        }
        return null;
    }
    
    /**
     * Create an AttachmentMarshaller to marshal MTOM/SWA Attachments
     * @param writer
     * @return
     */
    protected AttachmentMarshaller createAttachmentMarshaller(XMLStreamWriter writer) {
        return new JAXBAttachmentMarshaller(getMessageContext(), writer);
    }
    
    /**
     * Create an Attachment unmarshaller for unmarshalling MTOM/SWA Attachments
     * @return AttachmentUnmarshaller
     */
    protected AttachmentUnmarshaller createAttachmentUnmarshaller(XMLStreamReader reader) {
        return new JAXBAttachmentUnmarshaller(getMessageContext(), reader);
    }

    /**
     * Unmarshal the xml into a JAXB object
     * @param reader
     * @return
     * @throws JAXBException
     */
    public Object unmarshal(XMLStreamReader reader) throws JAXBException {

        // 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 = createAttachmentUnmarshaller(reader);

        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 {
            // 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.
            Marshaller m = JAXBUtils.getJAXBMarshaller(getJAXBContext(cl));
            
            AttachmentMarshaller am = createAttachmentMarshaller(writer);
            if (am != null) {
                if (DEBUG_ENABLED) {
                    log.debug("Adding JAXBAttachmentMarshaller to Marshaller");
                }
                m.setAttachmentMarshaller(am);
            }


            // Marshal the object
            if (getProcessType() == null) {
                marshalByElement(obj, 
                                 m, 
                                 writer, 
                                 true);
                                 //!am.isXOPPackage());
            } else {
                marshalByType(obj,
                              m,
                              writer,
                              getProcessType(),
                              isxmlList(),
                              getConstructionType());
            }

            // Successfully marshalled the data
            JAXBUtils.releaseJAXBMarshaller(getJAXBContext(cl), m);
    }
    
    
    /**
     * 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 Object 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 {
                    if (!optimize) {
                        log.trace(JavaUtils.stackToString());
                        getOutputStream(writer);
                    }
                    OutputStream os = (optimize) ? getOutputStream(writer) : 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;
            }});
    }
    
    private static String getDebugName(Object o) {
        return (o == null) ? "null" : o.getClass().getCanonicalName();
    }

    /**
     * If the writer is backed by an OutputStream, then return the OutputStream
     * @param writer
     * @return OutputStream or null
     */
    private static OutputStream getOutputStream(XMLStreamWriter writer) 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);
            }
        }
        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.");
        }

        return AccessController.doPrivileged(new PrivilegedAction() {
            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) {
                                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
                                // I will first unmarshall the xmldata to a String[]
                                // Then use the unmarshalled jaxbElement to create
                                // proper type Object Array.
                                
                                jaxb = unmarshalArray(reader, u, type);
                                
                            } else {
                                
                                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("unmarshalByType. Unmarshalling " + type.getName()
                                        + " as Enum");
                            }

                            JAXBElement<String> enumValue = u.unmarshal(reader, String.class);

                            if (enumValue != null) {
                                Method m =
                                        type.getMethod("fromValue", new Class[] { String.class });
                                jaxb = m.invoke(null, new Object[] { enumValue.getValue() });
                            } else {
                                jaxb = null;
                            }
                        }
                        //Normal case: We are not unmarshalling a xsd:list or Array
                        else {
                            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
                        
                        jaxb = unmarshalAsListOrArray(reader, u, type);
                        
                    }
                    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() {
                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 unmarshaArray");
            }
            
            // 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() {
                    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;
    }

    /**
     * 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
     */
    private static void marshalByType(final Object b, final Marshaller m,
                                      final XMLStreamWriter writer, final Class type,
                                      final boolean isList, final JAXBUtils.CONSTRUCTION_TYPE ctype)
        throws WebServiceException {
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object 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 (isList) {
                        if (DEBUG_ENABLED) {
                            log.debug("marshalling type which is a List or Array");
                        }
                        
                        // 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(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(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(jbe.getName(), String.class, value);
                        }
                    }

                    if (DEBUG_ENABLED) {
                        log.debug("Invoking marshalByType.  " +
                                        "Marshaling to an XMLStreamWriter. Object is "
                                + getDebugName(b));
                    }
                    
                    /// TODO 
                    // For the cases like enum and list, should we 
                    // intercept exceptions and try a different approach ?
                    m.marshal(jbo, writer);

                } catch (OMException e) {
                    throw e;
                } catch (Throwable t) {
                    throw new OMException(t);
                }
                return null;
            }
        });
    }

    /**
     * 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() {
                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);
        }
    }

}
