/*
 * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Blaise Doughan - 2.5 - initial implementation
package org.eclipse.persistence.internal.oxm;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;

import javax.xml.namespace.QName;
import javax.xml.transform.Result;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;

import org.eclipse.persistence.core.queries.CoreAttributeGroup;
import org.eclipse.persistence.core.sessions.CoreSession;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.localization.JAXBLocalization;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.record.AbstractMarshalRecord;
import org.eclipse.persistence.internal.oxm.record.ExtendedResult;
import org.eclipse.persistence.internal.oxm.record.namespaces.PrefixMapperNamespaceResolver;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.oxm.JSONWithPadding;
import org.eclipse.persistence.oxm.attachment.XMLAttachmentMarshaller;
import org.eclipse.persistence.oxm.record.ContentHandlerRecord;
import org.eclipse.persistence.oxm.record.FormattedOutputStreamRecord;
import org.eclipse.persistence.oxm.record.FormattedWriterRecord;
import org.eclipse.persistence.oxm.record.JSONFormattedWriterRecord;
import org.eclipse.persistence.oxm.record.JSONWriterRecord;
import org.eclipse.persistence.oxm.record.MarshalRecord;
import org.eclipse.persistence.oxm.record.NodeRecord;
import org.eclipse.persistence.oxm.record.OutputStreamRecord;
import org.eclipse.persistence.oxm.record.ValidatingMarshalRecord;
import org.eclipse.persistence.oxm.record.WriterRecord;
import org.eclipse.persistence.oxm.schema.XMLSchemaReference;
import org.eclipse.persistence.platform.xml.XMLPlatform;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.eclipse.persistence.platform.xml.XMLTransformer;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.ext.LexicalHandler;

public abstract class XMLMarshaller<
    ABSTRACT_SESSION extends CoreAbstractSession,
    CHARACTER_ESCAPE_HANDLER extends CharacterEscapeHandler,
    CONTEXT extends Context<ABSTRACT_SESSION, DESCRIPTOR, ?, ?, ?, SESSION, ?>,
    DESCRIPTOR extends Descriptor<?, ?, ?, ?, ?, ?, ?, ?, ?, ?>,
    MARSHALLER_LISTENER extends Marshaller.Listener,
    MEDIA_TYPE extends MediaType,
    NAMESPACE_PREFIX_MAPPER extends NamespacePrefixMapper,
    OBJECT_BUILDER extends ObjectBuilder<?, ABSTRACT_SESSION, ?, XMLMarshaller>,
    SESSION extends CoreSession> extends Marshaller<CHARACTER_ESCAPE_HANDLER, CONTEXT, MARSHALLER_LISTENER, MEDIA_TYPE, NAMESPACE_PREFIX_MAPPER> {

    protected final static String DEFAULT_XML_VERSION = "1.0";
    private static final String STAX_RESULT_CLASS_NAME = "javax.xml.transform.stax.StAXResult";
    private static final String GET_XML_STREAM_WRITER_METHOD_NAME = "getXMLStreamWriter";
    private static final String GET_XML_EVENT_WRITER_METHOD_NAME = "getXMLEventWriter";
    private static final String XML_STREAM_WRITER_RECORD_CLASS_NAME = "org.eclipse.persistence.oxm.record.XMLStreamWriterRecord";
    private static final String XML_EVENT_WRITER_RECORD_CLASS_NAME = "org.eclipse.persistence.oxm.record.XMLEventWriterRecord";
    private static final String XML_STREAM_WRITER_CLASS_NAME = "javax.xml.stream.XMLStreamWriter";
    private static final String XML_EVENT_WRITER_CLASS_NAME = "javax.xml.stream.XMLEventWriter";
    private static final String DOM_TO_STREAM_WRITER_CLASS_NAME = "org.eclipse.persistence.internal.oxm.record.DomToXMLStreamWriter";
    private static final String DOM_TO_EVENT_WRITER_CLASS_NAME = "org.eclipse.persistence.internal.oxm.record.DomToXMLEventWriter";
    private static final String WRITE_TO_STREAM_METHOD_NAME = "writeToStream";
    private static final String WRITE_TO_EVENT_WRITER_METHOD_NAME = "writeToEventWriter";

    protected static Class staxResultClass;
    protected static Method staxResultGetStreamWriterMethod;
    protected static Method staxResultGetEventWriterMethod;
    private static Constructor xmlStreamWriterRecordConstructor;
    private static Constructor xmlEventWriterRecordConstructor;
    protected static Method writeToStreamMethod;
    protected static Method writeToEventWriterMethod;
    protected static Class domToStreamWriterClass;
    protected static Class domToEventWriterClass;

    static {
        try {
            staxResultClass = PrivilegedAccessHelper.getClassForName(STAX_RESULT_CLASS_NAME);
            if(staxResultClass != null) {
                staxResultGetStreamWriterMethod = PrivilegedAccessHelper.getDeclaredMethod(staxResultClass, GET_XML_STREAM_WRITER_METHOD_NAME, new Class[]{});
                staxResultGetEventWriterMethod = PrivilegedAccessHelper.getDeclaredMethod(staxResultClass, GET_XML_EVENT_WRITER_METHOD_NAME, new Class[]{});
            }
            Class streamWriterRecordClass = PrivilegedAccessHelper.getClassForName(XML_STREAM_WRITER_RECORD_CLASS_NAME);
            Class streamWriterClass = PrivilegedAccessHelper.getClassForName(XML_STREAM_WRITER_CLASS_NAME);
            xmlStreamWriterRecordConstructor = PrivilegedAccessHelper.getConstructorFor(streamWriterRecordClass, new Class[]{streamWriterClass}, true);

            Class eventWriterRecordClass = PrivilegedAccessHelper.getClassForName(XML_EVENT_WRITER_RECORD_CLASS_NAME);
            Class eventWriterClass = PrivilegedAccessHelper.getClassForName(XML_EVENT_WRITER_CLASS_NAME);
            xmlEventWriterRecordConstructor = PrivilegedAccessHelper.getConstructorFor(eventWriterRecordClass, new Class[]{eventWriterClass}, true);

            domToStreamWriterClass = PrivilegedAccessHelper.getClassForName(DOM_TO_STREAM_WRITER_CLASS_NAME);
            writeToStreamMethod = PrivilegedAccessHelper.getMethod(domToStreamWriterClass, WRITE_TO_STREAM_METHOD_NAME, new Class[] {CoreClassConstants.NODE, CoreClassConstants.STRING, CoreClassConstants.STRING, streamWriterClass}, true);

            domToEventWriterClass = PrivilegedAccessHelper.getClassForName(DOM_TO_EVENT_WRITER_CLASS_NAME);
            writeToEventWriterMethod = PrivilegedAccessHelper.getMethod(domToEventWriterClass, WRITE_TO_EVENT_WRITER_METHOD_NAME, new Class[] {CoreClassConstants.NODE, CoreClassConstants.STRING, CoreClassConstants.STRING, eventWriterClass}, true);

        } catch (Exception ex) {
            // Do nothing
        }
    }

    protected XMLAttachmentMarshaller attachmentMarshaller;
    private String attributePrefix;
    private boolean fragment;
    private boolean includeRoot = true;
    private boolean marshalEmptyCollections = true;
    protected MEDIA_TYPE mediaType;
    private char namespaceSeparator;
    private String noNamespaceSchemaLocation;
    private boolean reduceAnyArrays;
    private Schema schema;
    private String schemaLocation;
    protected XMLTransformer transformer;
    private String valueWrapper;
    private boolean wrapperAsCollectionName = false;
    private String xmlHeader;
    private Object marshalAttributeGroup;
    private Boolean logPayload;

    public XMLMarshaller(CONTEXT context) {
        super(context);
        this.includeRoot = true;
        this.marshalEmptyCollections = true;
        this.namespaceSeparator = Constants.DOT;
        this.reduceAnyArrays = false;
        this.valueWrapper = Constants.VALUE_WRAPPER;
    }

    /**
     * Copy constructor
     */
    protected XMLMarshaller(XMLMarshaller xmlMarshaller) {
        super(xmlMarshaller);
        attachmentMarshaller = xmlMarshaller.getAttachmentMarshaller();
        attributePrefix = xmlMarshaller.getAttributePrefix();
        fragment = xmlMarshaller.isFragment();
        includeRoot = xmlMarshaller.isIncludeRoot();
        marshalEmptyCollections = xmlMarshaller.isMarshalEmptyCollections();
        mediaType = (MEDIA_TYPE) xmlMarshaller.mediaType;
        namespaceSeparator = xmlMarshaller.getNamespaceSeparator();
        noNamespaceSchemaLocation = xmlMarshaller.getNoNamespaceSchemaLocation();
        reduceAnyArrays = xmlMarshaller.isReduceAnyArrays();

        if(null != xmlMarshaller.getSchema()) {
            setSchema(xmlMarshaller.getSchema());
        }

        schemaLocation = xmlMarshaller.getSchemaLocation();
        valueWrapper = xmlMarshaller.getValueWrapper();
        wrapperAsCollectionName = xmlMarshaller.isWrapperAsCollectionName();
        xmlHeader = xmlMarshaller.getXmlHeader();
    }

    protected void addDescriptorNamespacesToXMLRecord(DESCRIPTOR xmlDescriptor, AbstractMarshalRecord record) {
        if (null == xmlDescriptor) {
            return;
        }
        copyNamespaces(xmlDescriptor.getNamespaceResolver(), record.getNamespaceResolver());
    }

    private XPathFragment buildRootFragment(Object object, DESCRIPTOR descriptor, boolean isXMLRoot, MarshalRecord marshalRecord) {
        XPathFragment rootFragment = null;
        if (isXMLRoot) {
            String xmlRootUri = ((Root) object).getNamespaceURI();
            String xmlRootLocalName = ((Root) object).getLocalName();
            rootFragment = new XPathFragment();
            rootFragment.setLocalName(xmlRootLocalName);
            rootFragment.setNamespaceURI(xmlRootUri);
            rootFragment.setNamespaceAware(marshalRecord.isNamespaceAware());
            rootFragment.setNamespaceSeparator(marshalRecord.getNamespaceSeparator());

            if (xmlRootUri != null) {
                if (descriptor != null) {
                    String xmlRootPrefix = marshalRecord.getNamespaceResolver().resolveNamespaceURI(xmlRootUri);
                    if (xmlRootPrefix == null && !(xmlRootUri.equals(marshalRecord.getNamespaceResolver().getDefaultNamespaceURI()))) {
                        xmlRootPrefix = marshalRecord.getNamespaceResolver().generatePrefix();
                        marshalRecord.getNamespaceResolver().put(xmlRootPrefix, xmlRootUri);
                    }
                    if(xmlRootPrefix == null) {
                        rootFragment.setXPath(xmlRootLocalName);
                    } else {
                         rootFragment.setPrefix(xmlRootPrefix);
                    }
                } else {
                    if(marshalRecord.isNamespaceAware()){
                        String xmlRootPrefix = "ns0";
                        marshalRecord.getNamespaceResolver().put(xmlRootPrefix, xmlRootUri);
                        rootFragment.setXPath(xmlRootPrefix + marshalRecord.getNamespaceSeparator() + xmlRootLocalName);
                    }else{
                        rootFragment.setXPath(xmlRootLocalName);
                    }
                }
            }
        } else {
            Field defaultRootField = descriptor.getDefaultRootElementField();
            if(defaultRootField != null){
                rootFragment = defaultRootField.getXPathFragment();
            }
        }
        return rootFragment;
    }

    protected void copyNamespaces(NamespaceResolver source, NamespaceResolver target) {
        if (null != source && null != target) {
            if(source.hasPrefixesToNamespaces()) {
                target.getPrefixesToNamespaces().putAll(source.getPrefixesToNamespaces());
            }
            target.setDefaultNamespaceURI(source.getDefaultNamespaceURI());
        }
    }

    @Override
    public XMLAttachmentMarshaller getAttachmentMarshaller() {
        return this.attachmentMarshaller;
    }

    /**
     * Value that will be used to prefix attributes.
     * Ignored marshalling XML.
     * @since 2.4
     */
    public String getAttributePrefix() {
        return attributePrefix;
    }

    /**
     * INTERNAL:
     * Return the descriptor for the root object.
     */
     protected DESCRIPTOR getDescriptor(Class clazz, ABSTRACT_SESSION session) throws XMLMarshalException {
         DESCRIPTOR descriptor = (DESCRIPTOR) session.getDescriptor(clazz);
         if (descriptor == null) {
             throw XMLMarshalException.descriptorNotFoundInProject(clazz.getName());
         }

         return descriptor;
    }

     /**
      * INTERNAL:
      * Return the descriptor for the root object.
      */
      public DESCRIPTOR getDescriptor(Object object) throws XMLMarshalException {
          DESCRIPTOR descriptor = (DESCRIPTOR) context.getSession(object).getDescriptor(object);
          if (descriptor == null) {
              throw XMLMarshalException.descriptorNotFoundInProject(object.getClass().getName());
          }

          return descriptor;
      }

     /**
      * INTERNAL:
      * Return the descriptor for the root object.
      */
      protected DESCRIPTOR getDescriptor(Object object, ABSTRACT_SESSION session) throws XMLMarshalException {
          DESCRIPTOR descriptor = (DESCRIPTOR) session.getDescriptor(object);
          if (descriptor == null) {
              throw XMLMarshalException.descriptorNotFoundInProject(object.getClass().getName());
          }

          return descriptor;
      }

      protected DESCRIPTOR getDescriptor(Object object, boolean isXMLRoot) {
          if (isXMLRoot) {
              return getDescriptor((Root) object);
          } else {
              return getDescriptor(object);
          }
      }

      protected DESCRIPTOR getDescriptor(Root object) throws XMLMarshalException {
          DESCRIPTOR descriptor = null;

          try {
              ABSTRACT_SESSION session = context.getSession(object.getObject());
              if(null == session) {
                  return null;
              }
              descriptor = (DESCRIPTOR) session.getDescriptor(object.getObject());
          } catch (XMLMarshalException marshalException) {
              if ((descriptor == null) && isSimpleXMLRoot(object)) {
                  return null;
              }
              throw marshalException;
          }

          if (descriptor == null) {
              throw XMLMarshalException.descriptorNotFoundInProject(object.getClass().getName());
          }

          return descriptor;
      }

      protected DESCRIPTOR getDescriptor(Root object, ABSTRACT_SESSION session) throws XMLMarshalException {
          DESCRIPTOR descriptor = null;

          try {
              if(null == session) {
                  return null;
              }
              descriptor = (DESCRIPTOR) session.getDescriptor(object.getObject());
          } catch (XMLMarshalException marshalException) {
              if ((descriptor == null) && isSimpleXMLRoot(object)) {
                  return null;
              }
              throw marshalException;
          }

          if (descriptor == null) {
              throw XMLMarshalException.descriptorNotFoundInProject(object.getClass().getName());
          }

          return descriptor;
      }

    protected Node getNode(Object object, Node parentNode, ABSTRACT_SESSION session, DESCRIPTOR descriptor, boolean isRoot) {
        if(isRoot) {
            object = ((Root) object).getObject();
            if(object instanceof Node) {
                return (Node) object;
            }
        }
        return null;
    }

    /**
     * Get the no namespace schema location set on this XMLMarshaller
     * @return the no namespace schema location specified on this XMLMarshaller
     */
    public String getNoNamespaceSchemaLocation() {
       return noNamespaceSchemaLocation;
    }

    public Schema getSchema() {
        return schema;
    }

    /**
     * INTERNAL
     * @return the transformer instance for this marshaller
     */
    @Override
    public XMLTransformer getTransformer() {
        if(null == transformer) {
            XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
            transformer = xmlPlatform.newXMLTransformer();
            transformer.setEncoding(getEncoding());
            transformer.setFormattedOutput(isFormattedOutput());
            transformer.setFragment(fragment);

        }
        return transformer;
    }

    /**
     * Name of the property to marshal/unmarshal as a wrapper on the text() mappings
     * Ignored marshalling XML.
     * @since 2.4
     */
    public String getValueWrapper() {
        return valueWrapper;
    }

    /**
     * Get this Marshaller's XML Header.
     * @since 2.4
     */
    public String getXmlHeader() {
        return xmlHeader;
    }

    /**
     * Get the schema location set on this XMLMarshaller
     * @return the schema location specified on this XMLMarshaller
     */
    public String getSchemaLocation() {
       return schemaLocation;
    }

    /**
     * INTERNAL
     * @return true if the media type is application/json, else false.
     * @since EclipseLink 2.6.0
     */
    @Override
    public boolean isApplicationJSON() {
        return null != mediaType && mediaType.isApplicationJSON();
    }

    /**
     * INTERNAL
     * @return true if the media type is application/xml, else false.
     * @since EclipseLink 2.6.0
     */
    @Override
    public boolean isApplicationXML() {
        return null == mediaType || mediaType.isApplicationXML();
    }

    /**
     * PUBLIC:
     * Returns if this should marshal to a fragment.  If true an XML header string is not written out.
     * @return if this should marshal to a fragment or not
     */
    public boolean isFragment() {
        return isApplicationXML() && fragment;
    }

    /**
     * Determine if the @XMLRootElement should be marshalled when present.
     * Ignored marshalling XML.
     * @return
     * @since 2.4
     */
    @Override
    public boolean isIncludeRoot() {
       if(isApplicationJSON()){
           return includeRoot;
       }
       return true;
    }

    /**
     * Property to determine if size 1 any collections should be treated as collections
     * Ignored marshalling XML.
     */
    @Override
    public boolean isReduceAnyArrays() {
        return reduceAnyArrays;
    }

    /**
     * Get the namespace separator used during marshal operations.
     * If mediaType is application/json '.' is the default
     * Ignored marshalling XML.
     * @since 2.4
     */
    public char getNamespaceSeparator() {
        return namespaceSeparator;
    }

    /**
     * Name of the property to determine if empty collections should be marshalled as []
     * Ignored marshalling XML.
     * @since 2.4
     */
    public boolean isMarshalEmptyCollections() {
        return marshalEmptyCollections;
    }

    @Override
    public boolean isWrapperAsCollectionName() {
        return wrapperAsCollectionName;
    }

    protected boolean isSimpleXMLRoot(Root xmlRoot) {
        Class xmlRootObjectClass = xmlRoot.getObject().getClass();

        ConversionManager conversionManager = (ConversionManager) context.getSession().getDatasourcePlatform().getConversionManager();
        if (conversionManager.schemaType(xmlRootObjectClass) != null || CoreClassConstants.List_Class.isAssignableFrom(xmlRootObjectClass) || CoreClassConstants.XML_GREGORIAN_CALENDAR.isAssignableFrom(xmlRootObjectClass) || CoreClassConstants.DURATION.isAssignableFrom(xmlRootObjectClass)) {
            return true;
        } else if(xmlRoot.getObject() instanceof org.w3c.dom.Node) {
            return true;
        }
        return false;
    }

    /**
     * PUBLIC:
     * Convert the given object to XML and update the given contentHandler with that XML Document
     * @param object the object to marshal
     * @param contentHandler the contentHandler which the specified object should be marshalled to
     * @throws XMLMarshalException if an error occurred during marshalling
     */
    public void marshal(Object object, ContentHandler contentHandler) throws XMLMarshalException {
        if (contentHandler instanceof LexicalHandler) {
            marshal(object, contentHandler, (LexicalHandler)contentHandler);
        } else {
            marshal(object, contentHandler, null);
        }
    }

    /**
     * PUBLIC:
     * Convert the given object to XML and update the given contentHandler with that XML Document
     * @param object the object to marshal
     * @param contentHandler the contentHandler which the specified object should be marshalled to
     * @throws XMLMarshalException if an error occurred during marshalling
     */
    public void marshal(Object object, ContentHandler contentHandler, LexicalHandler lexicalHandler) throws XMLMarshalException {
        if(object instanceof JSONWithPadding && !isApplicationJSON()){
            object = ((JSONWithPadding)object).getObject();
        }

        if ((object == null) || (contentHandler == null)) {
            throw XMLMarshalException.nullArgumentException();
        }

        ABSTRACT_SESSION session = null;
        DESCRIPTOR xmlDescriptor = null;

        boolean isXMLRoot = (object instanceof Root);
        if(isXMLRoot){
            try{
                session = context.getSession(((Root)object).getObject());
                if(session != null){
                    xmlDescriptor = getDescriptor(((Root)object).getObject(), session);
                }
            }catch (XMLMarshalException marshalException) {
                if (!isSimpleXMLRoot((Root) object)) {
                    throw marshalException;
                }
            }
        }else{
            Class objectClass = object.getClass();
            session = context.getSession(objectClass);
            xmlDescriptor = getDescriptor(objectClass, session);
        }

        ContentHandlerRecord contentHandlerRecord = new ContentHandlerRecord();
        contentHandlerRecord.setMarshaller(this);
        contentHandlerRecord.setContentHandler(contentHandler);
        contentHandlerRecord.setLexicalHandler(lexicalHandler);
        marshal(object, contentHandlerRecord, session, xmlDescriptor,isXMLRoot);
    }

    /**
     * Convert the given object to XML and update the given marshal record with
     * that XML Document.
     * @param object the object to marshal
     * @param marshalRecord the marshalRecord to marshal the object to
     */
    public void marshal(Object object, MarshalRecord marshalRecord) {
        if(object instanceof JSONWithPadding && !isApplicationJSON()){
            object = ((JSONWithPadding)object).getObject();
        }
        if ((object == null) || (marshalRecord == null)) {
            throw XMLMarshalException.nullArgumentException();
        }

        boolean isXMLRoot = (object instanceof Root);

        ABSTRACT_SESSION session = null;
        DESCRIPTOR xmlDescriptor = null;
        if(isXMLRoot){
            try{
                session = context.getSession(((Root)object).getObject());
                if(session != null){
                    xmlDescriptor = getDescriptor(((Root)object).getObject(), session);
                }
            }catch (XMLMarshalException marshalException) {
                if (!isSimpleXMLRoot((Root) object)) {
                    throw marshalException;
                }
            }
        }else{
            Class objectClass = object.getClass();
            session = context.getSession(objectClass);
            xmlDescriptor = getDescriptor(objectClass, session);
        }

        marshal(object, marshalRecord, session, xmlDescriptor, isXMLRoot);
    }

    /**
     * Convert the given object to XML and update the given marshal record with
     * that XML Document.
     * @param object the object to marshal
     * @param marshalRecord the marshalRecord to marshal the object to
     * @param descriptor the XMLDescriptor for the object being marshalled
     */
    protected void marshal(Object object, MarshalRecord marshalRecord, ABSTRACT_SESSION session, DESCRIPTOR descriptor, boolean isXMLRoot) {
        SessionLog logger = AbstractSessionLog.getLog();

        if (logger.shouldLog(SessionLog.FINE, SessionLog.MOXY)) {
            logger.log(SessionLog.FINE, SessionLog.MOXY, "moxy_start_marshalling", new Object[] { (object!= null)?object.getClass().getName():"N/A", this.mediaType});
        }
        if (object != null && logPayload != null && this.isLogPayload()) {
                AbstractSessionLog.getLog().log(SessionLog.FINEST, SessionLog.MOXY, object.toString(), new Object[0], false);
        }

        if(null != schema) {
            marshalRecord = new ValidatingMarshalRecord(marshalRecord, this);
        }
        if (this.attachmentMarshaller != null) {
            marshalRecord.setXOPPackage(this.attachmentMarshaller.isXOPPackage());
        }
        marshalRecord.setMarshaller(this);

        Root root = null;
        if(isXMLRoot) {
            root = (Root) object;
        }
        Node node = getNode(object, marshalRecord.getDOM(), session, descriptor, isXMLRoot);

        if(this.mapper == null) {
            if(null == node) {
                addDescriptorNamespacesToXMLRecord(descriptor, marshalRecord);
            }
        } else {
            if(descriptor == null || null != node){
                marshalRecord.setNamespaceResolver(new PrefixMapperNamespaceResolver(mapper, null));
            }else{
                marshalRecord.setNamespaceResolver(new PrefixMapperNamespaceResolver(mapper, descriptor.getNamespaceResolver()));
            }
            marshalRecord.setCustomNamespaceMapper(true);
        }

        if(this.getMarshalAttributeGroup() != null) {
            if(marshalAttributeGroup.getClass() == CoreClassConstants.STRING) {
                CoreAttributeGroup group = descriptor.getAttributeGroup((String)marshalAttributeGroup);
                if(group != null) {
                    marshalRecord.pushAttributeGroup(group);
                } else {
                    throw XMLMarshalException.invalidAttributeGroupName((String)marshalAttributeGroup, descriptor.getJavaClassName());
                }
            } else if(marshalAttributeGroup instanceof CoreAttributeGroup) {
                marshalRecord.pushAttributeGroup((CoreAttributeGroup)marshalAttributeGroup);
            } else {
                //Error case
            }
        }
        NamespaceResolver nr = marshalRecord.getNamespaceResolver();
        if(node != null) {
            if(isXMLRoot) {
                if (isFragment()) {
                    marshalRecord.node(node, null,  root.getNamespaceURI(), root.getLocalName());
               } else {
                   String encoding = root.getEncoding();
                   if(null == encoding) {
                       encoding = Constants.DEFAULT_XML_ENCODING;
                   }
                   String version = root.getXMLVersion();
                   if(null == version) {
                       version = DEFAULT_XML_VERSION;
                   }
                   marshalRecord.startDocument(encoding, version);
                   marshalRecord.node(node, marshalRecord.getNamespaceResolver(), root.getNamespaceURI(), root.getLocalName());
                   marshalRecord.endDocument();
               }
            } else {
                marshalRecord.node(node, nr);
            }
            marshalRecord.flush();
            return;
        }

        if(isXMLRoot){
            if(descriptor != null){
                marshalRecord.beforeContainmentMarshal(root.getObject());
            }
        }else{
            marshalRecord.beforeContainmentMarshal(object);
        }

        if (!isFragment()) {
            String encoding = getEncoding();
            String version = DEFAULT_XML_VERSION;
            if (!isXMLRoot && descriptor!= null) {
                marshalRecord.setLeafElementType(descriptor.getDefaultRootElementType());
            } else {
                if (root.getEncoding() != null) {
                    encoding = root.getEncoding();
                }
                if (root.getXMLVersion() != null) {
                    version = root.getXMLVersion();
                }
            }
            marshalRecord.startDocument(encoding, version);
        }
        if (getXmlHeader() != null) {
            marshalRecord.writeHeader();
        }
        if(isXMLRoot) {
            if(root.getObject() instanceof Node) {
                marshalRecord.node((Node)root.getObject(), new NamespaceResolver(), root.getNamespaceURI(), root.getLocalName());
                marshalRecord.endDocument();
                return;
            }
        }
        XPathFragment rootFragment = buildRootFragment(object, descriptor, isXMLRoot, marshalRecord);

        String schemaLocation = getSchemaLocation();
        String noNsSchemaLocation = getNoNamespaceSchemaLocation();
        boolean isNil = false;
        if (isXMLRoot) {
            object = root.getObject();
            if (root.getSchemaLocation() != null) {
                schemaLocation = root.getSchemaLocation();
            }
            if (root.getNoNamespaceSchemaLocation() != null) {
                noNsSchemaLocation = root.getNoNamespaceSchemaLocation();
            }
            marshalRecord.setLeafElementType(root.getSchemaType());
            isNil = root.isNil();
        }

        String xsiPrefix = null;
        if ((null != getSchemaLocation()) || (null != getNoNamespaceSchemaLocation()) || (isNil)) {
            xsiPrefix = nr.resolveNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
            if (null == xsiPrefix) {
                xsiPrefix = Constants.SCHEMA_INSTANCE_PREFIX;
                nr.put(Constants.SCHEMA_INSTANCE_PREFIX, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
            }
        }

        OBJECT_BUILDER treeObjectBuilder = null;
        if (descriptor != null) {
            treeObjectBuilder = (OBJECT_BUILDER) descriptor.getObjectBuilder();
        }
        if(session == null){
            session = (ABSTRACT_SESSION) context.getSession();
        }
        marshalRecord.setSession(session);

        if (null != rootFragment && !(rootFragment.getLocalName().equals(Constants.EMPTY_STRING))) {
            marshalRecord.startPrefixMappings(nr);
            if (!isXMLRoot && descriptor != null && descriptor.getNamespaceResolver() == null && rootFragment.hasNamespace()) {
                // throw an exception if the name has a : in it but the namespaceresolver is null
                throw XMLMarshalException.namespaceResolverNotSpecified(rootFragment.getShortName());
            }

            if(isIncludeRoot()){
                marshalRecord.openStartElement(rootFragment, nr);
            }
            if (null != schemaLocation) {
                marshalRecord.attributeWithoutQName(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_LOCATION, xsiPrefix, schemaLocation);
            }
            if (null != noNsSchemaLocation) {
                marshalRecord.attributeWithoutQName(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.NO_NS_SCHEMA_LOCATION, xsiPrefix, noNsSchemaLocation);
            }
            if (isNil) {
                marshalRecord.nilSimple(nr);
            }

            marshalRecord.namespaceDeclarations(nr);

            if (descriptor != null && !isNil) {
                marshalRecord.addXsiTypeAndClassIndicatorIfRequired(descriptor, null, descriptor.getDefaultRootElementField(), root, object, isXMLRoot, true);
                treeObjectBuilder.marshalAttributes(marshalRecord, object, session);
            }

            if(isIncludeRoot()) {
                marshalRecord.closeStartElement();
            }
        }else{
            //no rootfragment
            marshalRecord.marshalWithoutRootElement(treeObjectBuilder,object, descriptor, root, isXMLRoot);
        }
        if (treeObjectBuilder != null && !isNil) {
            treeObjectBuilder.buildRow(marshalRecord, object, session, this, rootFragment);
        } else if (isXMLRoot) {
             if(object != null && !isNil) {
                 if(root.getDeclaredType() != null && root.getObject() != null && root.getDeclaredType() != root.getObject().getClass()) {
                      QName type = marshalRecord.getConversionManager().schemaType(object.getClass());
                      if(type != null) {
                          xsiPrefix = nr.resolveNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
                          if (null == xsiPrefix) {
                              xsiPrefix = Constants.SCHEMA_INSTANCE_PREFIX;
                              marshalRecord.namespaceDeclaration(xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
                          }
                          marshalRecord.namespaceDeclaration(Constants.SCHEMA_PREFIX, javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
                          String typeValue = type.getLocalPart();
                          if(marshalRecord.isNamespaceAware() && (mediaType.isApplicationXML() || getJsonTypeConfiguration().useXsdTypesWithPrefix())){
                              typeValue = Constants.SCHEMA_PREFIX + marshalRecord.getNamespaceSeparator() + typeValue;
                          }
                          marshalRecord.attribute(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_TYPE_ATTRIBUTE, xsiPrefix + Constants.COLON + Constants.SCHEMA_TYPE_ATTRIBUTE, typeValue);
                      }
                 }

                marshalRecord.characters(root.getSchemaType(), object, null, false);
            }
        }

        if (null != rootFragment && !(rootFragment.getLocalName().equals(Constants.EMPTY_STRING)) && isIncludeRoot()) {
            marshalRecord.endElement(rootFragment, nr);
            marshalRecord.endPrefixMappings(nr);
        }
        if (!isFragment() ) {
            marshalRecord.endDocument();
        }
        if(isXMLRoot){
            if(descriptor != null){
                marshalRecord.afterContainmentMarshal(null, root.getObject());
            }
        }else{
            marshalRecord.afterContainmentMarshal(null, object);
        }
        marshalRecord.flush();
    }

    /**
     * PUBLIC:
     * @param object the object to marshal
     * @param node the node which the specified object should be marshalled to
     * @throws XMLMarshalException if an error occurred during marshalling
     */
    public void marshal(Object object, Node node) throws XMLMarshalException {
        if(object instanceof JSONWithPadding && !isApplicationJSON()){
            object = ((JSONWithPadding)object).getObject();
        }

        if ((object == null) || (node == null)) {
            throw XMLMarshalException.nullArgumentException();
        }

        ABSTRACT_SESSION session = null;
        DESCRIPTOR xmlDescriptor = null;

        boolean isXMLRoot = (object instanceof Root);
        if(isXMLRoot){
            try{
                session = context.getSession(((Root)object).getObject());
                if(session != null){
                    xmlDescriptor = getDescriptor(((Root)object).getObject(), session);
                }
            }catch (XMLMarshalException marshalException) {
                if (!isSimpleXMLRoot((Root) object)) {
                    throw marshalException;
                }
            }
        }else{
            Class objectClass = object.getClass();
            session = context.getSession(objectClass);
            xmlDescriptor = getDescriptor(objectClass, session);
        }

        NodeRecord contentHandlerRecord = new NodeRecord(node);
        contentHandlerRecord.setMarshaller(this);
        if (!isXMLRoot) {
            if ((null == xmlDescriptor.getDefaultRootElement()) && (node.getNodeType() == Node.ELEMENT_NODE) && (xmlDescriptor.getSchemaReference() != null) && (xmlDescriptor.getSchemaReference().getType() == XMLSchemaReference.COMPLEX_TYPE)) {
                Attr typeAttr = ((Element) node).getAttributeNodeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_TYPE_ATTRIBUTE);
                if (typeAttr == null) {
                    NamespaceResolver namespaceResolver = xmlDescriptor.getNonNullNamespaceResolver();
                    String xsiPrefix = namespaceResolver.resolveNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);

                    if (null == xsiPrefix) {
                        xsiPrefix = namespaceResolver.generatePrefix(Constants.SCHEMA_INSTANCE_PREFIX);
                    }

                    String value = xmlDescriptor.getSchemaReference().getSchemaContext();

                    ((Element) node).setAttributeNS(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE + Constants.COLON + xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
                    ((Element) node).setAttributeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, xsiPrefix + Constants.COLON + Constants.SCHEMA_TYPE_ATTRIBUTE, value);

                } else {
                    String value = xmlDescriptor.getSchemaReference().getSchemaContext();
                    typeAttr.setValue(value);
                }
            }
        }
        marshal(object, contentHandlerRecord, session, xmlDescriptor,isXMLRoot);
    }

    /**
     * PUBLIC:
     * Convert the given object to XML and update the given outputStream with that XML Document
     * @param object the object to marshal
     * @param outputStream the outputStream to marshal the object to
     * @throws XMLMarshalException if an error occurred during marshalling
     */
    public void marshal(Object object, OutputStream outputStream) throws XMLMarshalException {
        marshal (object, outputStream, null, null);
    }

    private void marshal(Object object, OutputStream outputStream, ABSTRACT_SESSION session, DESCRIPTOR xmlDescriptor) throws XMLMarshalException {
        if ((object == null) || (outputStream == null)) {
            throw XMLMarshalException.nullArgumentException();
        }
        boolean isXMLRoot = false;
        String version = DEFAULT_XML_VERSION;
        String encoding = getEncoding();

        String callbackName = null;
        if(object instanceof JSONWithPadding){
            callbackName = ((JSONWithPadding)object).getCallbackName();
            object = ((JSONWithPadding)object).getObject();
            if(object == null){
                throw XMLMarshalException.nullArgumentException();
            }
        }

        if (object instanceof Root) {
            isXMLRoot = true;
            Root xroot = (Root) object;
            encoding = xroot.getEncoding() != null ? xroot.getEncoding() : encoding;
        }

        if(!encoding.equals(Constants.DEFAULT_XML_ENCODING)) {
            try {
                OutputStreamWriter writer = new OutputStreamWriter(outputStream, encoding);
                marshal(object, writer, session, xmlDescriptor);
                writer.flush();
            } catch(EclipseLinkException e) {
                throw e;
            } catch(Exception e) {
                throw XMLMarshalException.marshalException(e);
            }
            return;
        }

        MarshalRecord marshalRecord;
        if (isFormattedOutput()) {
            if(isApplicationJSON()) {
                marshalRecord = new JSONFormattedWriterRecord(outputStream, callbackName);
            } else {
                marshalRecord = new FormattedOutputStreamRecord();
                ((FormattedOutputStreamRecord)marshalRecord).setOutputStream(outputStream);
            }
        } else {
            if(isApplicationJSON()) {
                marshalRecord = new JSONWriterRecord(outputStream, callbackName);
            } else {
                marshalRecord = new OutputStreamRecord();
                ((OutputStreamRecord)marshalRecord).setOutputStream(outputStream);
            }
        }

        marshalStreamOrWriter(object, marshalRecord, session, xmlDescriptor, isXMLRoot);
   }

    /**
     * PUBLIC:
     * Convert the given object to XML and update the given result with that XML Document
     * @param object the object to marshal
     * @param result the result to marshal the object to
     * @throws XMLMarshalException if an error occurred during marshalling
     */
    public void marshal(Object object, Result result) throws XMLMarshalException {
        if ((object == null) || (result == null)) {
            throw XMLMarshalException.nullArgumentException();
        }
        DESCRIPTOR xmlDescriptor = null;
        ABSTRACT_SESSION session = null;

        boolean isXMLRoot = (object instanceof Root);

        if(isXMLRoot){
            try{
                session = context.getSession(((Root)object).getObject());
                if(session != null){
                    xmlDescriptor = getDescriptor(((Root)object).getObject(), session);
                }
           }catch (XMLMarshalException marshalException) {
                if (!isSimpleXMLRoot((Root) object)) {
                    throw marshalException;
                }
            }
        }else{
            Class objectClass = object.getClass();
            session = context.getSession(objectClass);
            xmlDescriptor = getDescriptor(objectClass, session);
        }

        //if this is a simple xml root, the session and descriptor will be null
        if (result instanceof StreamResult) {
            StreamResult streamResult = (StreamResult) result;
            Writer writer = streamResult.getWriter();
            if (writer != null) {
                marshal(object, writer, session, xmlDescriptor);
            } else if (streamResult.getOutputStream() != null) {
                marshal(object, streamResult.getOutputStream(), session, xmlDescriptor);
            } else {
                try {
                    File f;
                    try {
                        f = new File(new URL(streamResult.getSystemId()).toURI());
                    } catch(MalformedURLException malformedURLException) {
                        try {
                            f = new File(streamResult.getSystemId());
                        } catch(Exception e) {
                            throw malformedURLException;
                        }
                    }
                    writer = new FileWriter(f);
                    try {
                        marshal(object, writer, session, xmlDescriptor);
                    } finally {
                        writer.close();
                    }
                } catch (Exception e) {
                    throw XMLMarshalException.marshalException(e);
                }
            }
        }else if (result instanceof DOMResult) {
            DOMResult domResult = (DOMResult) result;
            // handle case where the node is null
            if (domResult.getNode() == null) {
                domResult.setNode(this.objectToXML(object));
            } else {
                marshal(object, domResult.getNode());
            }
        } else if (result instanceof SAXResult) {
            SAXResult saxResult = (SAXResult) result;
            marshal(object, saxResult.getHandler());
        } else if (result instanceof ExtendedResult){
            marshal(object, ((ExtendedResult)result).createRecord(), session, xmlDescriptor, isXMLRoot);
        }else {
            if (result.getClass().equals(staxResultClass)) {
                try {
                    Object xmlStreamWriter = PrivilegedAccessHelper.invokeMethod(staxResultGetStreamWriterMethod, result);
                    if (xmlStreamWriter != null) {
                        MarshalRecord record = (MarshalRecord)PrivilegedAccessHelper.invokeConstructor(xmlStreamWriterRecordConstructor, new Object[]{xmlStreamWriter});
                        record.setMarshaller(this);
                        marshal(object, record, session, xmlDescriptor, isXMLRoot);
                        return;
                    } else {
                        Object xmlEventWriter = PrivilegedAccessHelper.invokeMethod(staxResultGetEventWriterMethod, result);
                        if(xmlEventWriter != null) {
                            MarshalRecord record = (MarshalRecord)PrivilegedAccessHelper.invokeConstructor(xmlEventWriterRecordConstructor, new Object[]{xmlEventWriter});
                            record.setMarshaller(this);
                            marshal(object, record, session, xmlDescriptor, isXMLRoot);
                            return;
                        }
                    }
                } catch(EclipseLinkException e) {
                    throw e;
                } catch (Exception e) {
                    throw XMLMarshalException.marshalException(e);
                }
            }
            java.io.StringWriter writer = new java.io.StringWriter();
            marshal(object, writer);
            javax.xml.transform.stream.StreamSource source = new javax.xml.transform.stream.StreamSource(new java.io.StringReader(writer.toString()));
            getTransformer().transform(source, result);
        }
        return;
    }

    /**
    * PUBLIC:
    * Convert the given object to XML and update the given writer with that XML Document
    * @param object the object to marshal
    * @param writer the writer to marshal the object to
    * @throws XMLMarshalException if an error occurred during marshalling
    */
    public void marshal(Object object, Writer writer) throws XMLMarshalException {
        marshal(object, writer, null, null);
    }

    private void marshal(Object object, Writer writer, ABSTRACT_SESSION session, DESCRIPTOR xmlDescriptor) throws XMLMarshalException {
        if ((object == null) || (writer == null)) {
            throw XMLMarshalException.nullArgumentException();
        }
        boolean isXMLRoot = false;
        String version = DEFAULT_XML_VERSION;
        String encoding = getEncoding();

        String callbackName = null;
        if(object instanceof JSONWithPadding){
            callbackName = ((JSONWithPadding)object).getCallbackName();
            object = ((JSONWithPadding)object).getObject();
            if(object == null){
                throw XMLMarshalException.nullArgumentException();
            }
        }

        if (object instanceof Root) {
            isXMLRoot = true;
            Root xroot = (Root) object;
            version = xroot.getXMLVersion() != null ? xroot.getXMLVersion() : version;
            encoding = xroot.getEncoding() != null ? xroot.getEncoding() : encoding;
        }

        MarshalRecord marshalRecord;
        writer = wrapWriter(writer);
        if (isFormattedOutput()) {
            if(isApplicationJSON()) {
                marshalRecord = new JSONFormattedWriterRecord(writer, callbackName);
            } else {
                marshalRecord = new FormattedWriterRecord();
                ((FormattedWriterRecord) marshalRecord).setWriter(writer);
            }
        } else {
            if(isApplicationJSON()) {
                marshalRecord = new JSONWriterRecord(writer, callbackName);
            } else {
                marshalRecord = new WriterRecord();
                ((WriterRecord) marshalRecord).setWriter(writer);
            }
        }

        marshalStreamOrWriter(object, marshalRecord, session, xmlDescriptor, isXMLRoot);
     }

    private void marshalStreamOrWriter(Object object, MarshalRecord marshalRecord, ABSTRACT_SESSION session, DESCRIPTOR descriptor, boolean isXMLRoot) {
        marshalRecord.setMarshaller(this);

        if(isXMLRoot){
            if(session == null || descriptor == null){
                try{
                    session = context.getSession(((Root)object).getObject());
                    if(session != null){
                        descriptor = getDescriptor(((Root)object).getObject(), session);
                    } else if (descriptor == null) {
                        descriptor = context.getDescriptor(new QName(((Root)object).getNamespaceURI(),((Root)object).getLocalName()));
                    }
                }catch (XMLMarshalException marshalException) {
                    if (!isSimpleXMLRoot((Root) object)) {
                        throw marshalException;
                    }
                }
            }
        }else{
            Class objectClass = object.getClass();
            if(object instanceof Collection) {
                int valueSize = ((Collection)object).size();
                if(marshalRecord.getMarshaller().isApplicationJSON() && (valueSize > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays())) {
                    marshalRecord.startCollection();
                }
                String valueWrapper;
                for(Object o : (Collection) object) {
                    if (o == null) {
                        valueWrapper = this.getValueWrapper();
                        if (isApplicationJSON()) {
                            this.setValueWrapper("");
                            marshalRecord.setMarshaller(this);
                        }
                        marshalRecord.nilSimple(null);
                        this.setValueWrapper(valueWrapper);
                    } else {
                        if (isApplicationJSON() && o != null && (o.getClass() == CoreClassConstants.STRING || o.getClass() == CoreClassConstants.BOOLEAN || CoreClassConstants.NUMBER.isAssignableFrom(o.getClass()))) {
                            if (marshalRecord.getSession() == null) {
                                marshalRecord.setSession((ABSTRACT_SESSION) this.getContext().getSession());
                            }
                            valueWrapper = this.getValueWrapper();
                            this.setValueWrapper("");
                            marshalRecord.setMarshaller(this);
                            marshalRecord.characters(null, o, null, false);
                            this.setValueWrapper(valueWrapper);
                        } else {
                            marshal(o, marshalRecord);
                        }
                    }
                }
                if(marshalRecord.getMarshaller().isApplicationJSON() && (valueSize > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays())) {
                    marshalRecord.endCollection();
                }
                marshalRecord.flush();
                return;
            } else if(objectClass.isArray()) {
                int arrayLength = Array.getLength(object);
                if(marshalRecord.getMarshaller().isApplicationJSON() && (arrayLength > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays())) {
                    marshalRecord.startCollection();
                }
                for(int x=0; x<arrayLength; x++) {
                    marshal(Array.get(object, x), marshalRecord);
                }
                if(marshalRecord.getMarshaller().isApplicationJSON() && (arrayLength > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays())) {
                    marshalRecord.endCollection();
                }
                marshalRecord.flush();
                return;
            }
            if(session == null || descriptor == null){
                session = context.getSession(objectClass);
                descriptor = getDescriptor(objectClass, session);
            }
        }

        marshal(object, marshalRecord, session, descriptor, isXMLRoot);
        marshalRecord.flush();
    }

    /**
     * INTERNAL:
     * Wrap Writer in a BufferedWriter only if its write() operations may be costly
     * (such as FileWriters and OutputStreamWriters).
     */
    private Writer wrapWriter(Writer writer) {
        if (writer instanceof OutputStreamWriter || writer instanceof FileWriter) {
            return new BufferedWriter(writer);
        }
        return writer;
    }

    /**
     * PUBLIC:
     * Convert the given object to an XML Document
     * @param object the object to marshal
     * @return the document which the specified object has been marshalled to
     * @throws XMLMarshalException if an error occurred during marshalling
     */
    public Document objectToXML(Object object) throws XMLMarshalException {
        boolean isXMLRoot = (object instanceof Root);
        DESCRIPTOR xmlDescriptor = getDescriptor(object, isXMLRoot);
        return objectToXML(object, xmlDescriptor, isXMLRoot);
    }

    /**
    * INTERNAL:
    * Convert the given object to an XML Document
    * @param object the object to marshal
    * @return the document which the specified object has been marshalled to
    * @param descriptor the XMLDescriptor for the object being marshalled
    * @throws XMLMarshalException if an error occurred during marshalling
    */
    protected Document objectToXML(Object object, DESCRIPTOR descriptor, boolean isXMLRoot) throws XMLMarshalException {
        ABSTRACT_SESSION session = context.getSession(descriptor);
        MarshalRecord marshalRecord = new NodeRecord();
        marshalRecord.setMarshaller(this);
        marshal(object, marshalRecord, session, descriptor, isXMLRoot);
        return marshalRecord.getDocument();
    }

    /**
     * INTERNAL:
     * Like ObjectToXML but is may also return a document fragment instead of a document in the
     * case of a non-root object.
     */
    protected Node objectToXMLNode(Object object, ABSTRACT_SESSION session, DESCRIPTOR descriptor, boolean isXMLRoot) throws XMLMarshalException {
        return objectToXMLNode(object, null, session, descriptor, isXMLRoot);
    }

    protected Node objectToXMLNode(Object object, Node rootNode, ABSTRACT_SESSION session, DESCRIPTOR descriptor, boolean isXMLRoot) throws XMLMarshalException {
        MarshalRecord marshalRecord = new NodeRecord();
        marshalRecord.setMarshaller(this);
        marshalRecord.getNamespaceResolver().setDOM(rootNode);
        marshal(object, marshalRecord, session, descriptor, isXMLRoot);
        return marshalRecord.getDocument();
    }

    public void setAttachmentMarshaller(XMLAttachmentMarshaller atm) {
        this.attachmentMarshaller = atm;
    }

    /**
     * Value that will be used to prefix attributes.
     * Ignored marshalling XML.
     * @since 2.4
     */
    public void setAttributePrefix(String attributePrefix) {
        this.attributePrefix = attributePrefix;
    }

    /**
     * Set the encoding on this XMLMarshaller
     * If the encoding is not set the default UTF-8 will be used
     * @param newEncoding the encoding to set on this XMLMarshaller
     */
    @Override
    public void setEncoding(String newEncoding) {
        super.setEncoding(newEncoding);
        if(null != transformer) {
            transformer.setEncoding(newEncoding);
        }
    }

    /**
     * Set if this XMLMarshaller should format the XML
     * By default this is set to true and the XML marshalled will be formatted.
     * @param shouldFormat if this XMLMarshaller should format the XML
     */
    @Override
    public void setFormattedOutput(boolean shouldFormat) {
        super.setFormattedOutput(shouldFormat);
        if(null != transformer) {
            transformer.setFormattedOutput(shouldFormat);
        }
    }

    /**
     * PUBLIC:
     * Set if this should marshal to a fragment.  If true an XML header string is not written out.
     * @param fragment if this should marshal to a fragment or not
     */
    public void setFragment(boolean fragment) {
        this.fragment = fragment;
        if(null != transformer) {
            transformer.setFragment(fragment);
        }
    }

    /**
     * Determine if the @XMLRootElement should be marshalled when present.
     * Ignored marshalling XML.
     * @return
     * @since 2.4
     */
    public void setIncludeRoot(boolean includeRoot) {
         this.includeRoot = includeRoot;
    }

    /**
     * Name of the property to determine if empty collections should be marshalled as []
     * Ignored marshalling XML.
     * @since 2.4
     */
    public void setMarshalEmptyCollections(Boolean marshalEmptyCollections) {
        this.marshalEmptyCollections = marshalEmptyCollections;
    }

    /**
     * Set the MediaType for this xmlMarshaller.
     * See org.eclipse.persistence.oxm.MediaType for the media types supported by EclipseLink MOXy
     * @param mediaType
     */
    public void setMediaType(MEDIA_TYPE mediaType) {
        this.mediaType = mediaType;
    }

    /**
     * Set the namespace separator used during marshal operations.
     * If mediaType is application/json '.' is the default
     * Ignored marshalling XML.
     * @since 2.4
     */
    public void setNamespaceSeparator(char namespaceSeparator) {
        this.namespaceSeparator = namespaceSeparator;
    }

   /**
     * Set the no namespace schema location on this XMLMarshaller
     * @param newNoNamespaceSchemaLocation no namespace schema location to be seton this XMLMarshaller
     */
    public void setNoNamespaceSchemaLocation(String newNoNamespaceSchemaLocation) {
       noNamespaceSchemaLocation = newNoNamespaceSchemaLocation;
    }

    /**
     * Property to determine if size 1 any collections should be treated as collections
     * Ignored marshalling XML.
     */
    public void setReduceAnyArrays(boolean reduceAnyArrays) {
        this.reduceAnyArrays = reduceAnyArrays;
    }

    public void setSchema(Schema schema) {
        this.schema = schema;
    }

    /**
     * Set the schema location on this XMLMarshaller
     * @param newSchemaLocation the schema location to be seton this XMLMarshaller
     */
    public void setSchemaLocation(String newSchemaLocation) {
       schemaLocation = newSchemaLocation;
    }

    public void setWrapperAsCollectionName(boolean wrapperAsCollectionName) {
        this.wrapperAsCollectionName = wrapperAsCollectionName;
    }

    /**
     * Name of the property to marshal/unmarshal as a wrapper on the text() mappings
     * Ignored marshalling XML.
     * @since 2.4
     */
    public void setValueWrapper(String valueWrapper) {
        this.valueWrapper = valueWrapper;
    }

    /**
     * <p>
     * Set this Marshaller's XML Header.  This header string will appear after
     * the XML processing instruction (&lt;?xml ...&gt;), but before the start
     * of the document's data.
     * </p>
     *
     * <p>
     * This feature is only supported when marshalling to Stream, Writer,
     * or StreamResult.
     * </p>
     * @since 2.4
     */
    public void setXmlHeader(String xmlHeader) {
        this.xmlHeader = xmlHeader;
    }

    public void setMarshalAttributeGroup(Object group) {
        this.marshalAttributeGroup = group;

    }

    public Object getMarshalAttributeGroup() {
        return this.marshalAttributeGroup;
    }

    public Boolean isLogPayload() {
        return logPayload;
    }

    public void setLogPayload(Boolean logPayload) {
        this.logPayload = logPayload;
    }
}
