/*
 * Copyright (c) 1998, 2018 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:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.sdo.helper.delegates;

import commonj.sdo.DataObject;
import commonj.sdo.Property;
import commonj.sdo.helper.HelperContext;
import commonj.sdo.helper.XMLDocument;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.WeakHashMap;

import javax.xml.namespace.QName;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.eclipse.persistence.sdo.SDOConstants;
import org.eclipse.persistence.sdo.SDOType;
import org.eclipse.persistence.sdo.SDOXMLDocument;
import org.eclipse.persistence.sdo.helper.SDOUnmappedContentHandler;
import org.eclipse.persistence.sdo.helper.SDOClassLoader;
import org.eclipse.persistence.sdo.helper.SDOMarshalListener;
import org.eclipse.persistence.sdo.helper.SDOTypeHelper;
import org.eclipse.persistence.sdo.helper.SDOUnmarshalListener;
import org.eclipse.persistence.sdo.helper.SDOXMLHelper;
import org.eclipse.persistence.sdo.types.SDOPropertyType;
import org.eclipse.persistence.sdo.types.SDOTypeType;
import org.eclipse.persistence.exceptions.SDOException;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.oxm.XMLConversionManager;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLContext;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.XMLLogin;
import org.eclipse.persistence.oxm.XMLMarshaller;
import org.eclipse.persistence.oxm.XMLRoot;
import org.eclipse.persistence.oxm.XMLUnmarshaller;
import org.eclipse.persistence.oxm.attachment.XMLAttachmentMarshaller;
import org.eclipse.persistence.oxm.attachment.XMLAttachmentUnmarshaller;
import org.eclipse.persistence.oxm.record.ContentHandlerRecord;
import org.eclipse.persistence.oxm.record.FormattedWriterRecord;
import org.eclipse.persistence.oxm.record.NodeRecord;
import org.eclipse.persistence.oxm.record.WriterRecord;
import org.eclipse.persistence.sessions.Project;
import org.xml.sax.InputSource;

/**
 * <p><b>Purpose</b>: Helper to XML documents into DataObects and DataObjects into XML documents.
 * <p><b>Responsibilities</b>:<ul>
 * <li> Load methods create commonj.sdo.XMLDocument objects from XML (unmarshal)
 * <li> Save methods create XML from commonj.sdo.XMLDocument and commonj.sdo.DataObject objects (marshal)
 * </ul>
 */
public class SDOXMLHelperDelegate implements SDOXMLHelper {
    private SDOClassLoader loader;
    private XMLContext xmlContext;

    private Map<Thread, XMLMarshaller> xmlMarshallerMap;
    private Map<Thread, XMLUnmarshaller> xmlUnmarshallerMap;

    private Project topLinkProject;

    // hold the context containing all helpers so that we can preserve inter-helper relationships
    private HelperContext aHelperContext;

    public SDOXMLHelperDelegate(HelperContext aContext) {
        this(aContext, Thread.currentThread().getContextClassLoader());
    }

    public SDOXMLHelperDelegate(HelperContext aContext, ClassLoader aClassLoader) {
        aHelperContext = aContext;
        // This ClassLoader is internal to SDO so no inter servlet-ejb container context issues should arise
        loader = new SDOClassLoader(aClassLoader, aContext);
        // WeakHashMap needs to be synchronized to prevent endless loop under the heavy load
        xmlMarshallerMap = Collections.synchronizedMap(new WeakHashMap<Thread, XMLMarshaller>());
        xmlUnmarshallerMap = Collections.synchronizedMap(new WeakHashMap<Thread, XMLUnmarshaller>());
    }

    /**
     * The specified TimeZone will be used for all String to date object
     * conversions.  By default the TimeZone from the JVM is used.
     */
    public void setTimeZone(TimeZone timeZone) {
        getXmlConversionManager().setTimeZone(timeZone);
    }

    /**
     * By setting this flag to true the marshalled date objects marshalled to
     * the XML schema types time and dateTime will be qualified by a time zone.
     * By default time information is not time zone qualified.
     */
    public void setTimeZoneQualified(boolean timeZoneQualified) {
        getXmlConversionManager().setTimeZoneQualified(timeZoneQualified);
    }

    /**
     * Creates and returns an XMLDocument from the input String.
     * By default does not perform XSD validation.
     * Same as
     *   load(new StringReader(inputString), null, null);
     *
     * @param inputString specifies the String to read from
     * @return the new XMLDocument loaded
     * @throws RuntimeException for errors in XML parsing or
     *    implementation-specific validation.
     */
    public XMLDocument load(String inputString) {
        StringReader reader = new StringReader(inputString);
        try {
            return load(reader, null, null);
        } catch (IOException ioException) {
            ioException.printStackTrace();
            return null;
        }
    }

    /**
     * Creates and returns an XMLDocument from the inputStream.
     * The InputStream will be closed after reading.
     * By default does not perform XSD validation.
     * Same as
     *   load(inputStream, null, null);
     *
     * @param inputStream specifies the InputStream to read from
     * @return the new XMLDocument loaded
     * @throws IOException for stream exceptions.
     * @throws RuntimeException for errors in XML parsing or
     *    implementation-specific validation.
     */
    public XMLDocument load(InputStream inputStream) throws IOException {
        return load(inputStream, null, null);
    }

    /**
     * Creates and returns an XMLDocument from the inputStream.
     * The InputStream will be closed after reading.
     * By default does not perform XSD validation.
     * @param inputStream specifies the InputStream to read from
     * @param locationURI specifies the URI of the document for relative schema locations
     * @param options implementation-specific options.
     * @return the new XMLDocument loaded
     * @throws IOException for stream exceptions.
     * @throws RuntimeException for errors in XML parsing or
     *    implementation-specific validation.
     */
    public XMLDocument load(InputStream inputStream, String locationURI, Object options) throws IOException {
        InputSource inputSource = new InputSource(inputStream);
        return load(inputSource, locationURI, options);
    }

    /**
       * Creates and returns an XMLDocument from the inputSource.
       * The InputSource will be closed after reading.
       * By default does not perform XSD validation.
       * @param inputSource specifies the InputSource to read from
       * @param locationURI specifies the URI of the document for relative schema locations
       * @param options implementation-specific options.
       * @return the new XMLDocument loaded
       * @throws IOException for stream exceptions.
       * @throws RuntimeException for errors in XML parsing or
       *    implementation-specific validation.
       */
    public XMLDocument load(InputSource inputSource, String locationURI, Object options) throws IOException {
        // get XMLUnmarshaller once - as we may create a new instance if this helper isDirty=true
        XMLUnmarshaller anXMLUnmarshaller = getXmlUnmarshaller(options);
        Object unmarshalledObject = null;
        if (options == null) {
            try {
               unmarshalledObject = anXMLUnmarshaller.unmarshal(inputSource);
            } catch(XMLMarshalException xmlException){
               handleXMLMarshalException(xmlException);
            }
        } else {
            try {
                DataObject optionsDataObject = (DataObject)options;
                try {
                    SDOType theType = (SDOType)optionsDataObject.get(SDOConstants.TYPE_LOAD_OPTION);
                    try{
                        if (theType != null) {
                            if(theType.isDataType()) {
                                theType = (SDOType)((SDOTypeHelper)this.aHelperContext.getTypeHelper()).getWrappersHashMap().get(theType.getQName());
                            }
                            if(theType != null) {
                                unmarshalledObject = anXMLUnmarshaller.unmarshal(inputSource, theType.getImplClass());
                            } else {
                                unmarshalledObject = anXMLUnmarshaller.unmarshal(inputSource);
                            }
                        }else{
                            unmarshalledObject = anXMLUnmarshaller.unmarshal(inputSource);
                        }
                    } catch(XMLMarshalException xmlException){
                        handleXMLMarshalException(xmlException);
                    }
                } catch (ClassCastException ccException) {
                  throw SDOException.typePropertyMustBeAType(ccException);
                }
            } catch (ClassCastException ccException) {
               throw SDOException.optionsMustBeADataObject(ccException, SDOConstants.ORACLE_SDO_URL ,SDOConstants.XMLHELPER_LOAD_OPTIONS);
            }
        }

        if (unmarshalledObject instanceof XMLRoot) {
            XMLRoot xmlRoot = (XMLRoot)unmarshalledObject;
            XMLDocument xmlDocument = createDocument((DataObject)((XMLRoot)unmarshalledObject).getObject(), ((XMLRoot)unmarshalledObject).getNamespaceURI(), ((XMLRoot)unmarshalledObject).getLocalName());
            if(xmlRoot.getEncoding() != null) {
                xmlDocument.setEncoding(xmlRoot.getEncoding());
            }
            if(xmlRoot.getXMLVersion() != null) {
                xmlDocument.setXMLVersion(xmlRoot.getXMLVersion());
            }
            xmlDocument.setSchemaLocation(xmlRoot.getSchemaLocation());
            xmlDocument.setNoNamespaceSchemaLocation(xmlRoot.getNoNamespaceSchemaLocation());
            return xmlDocument;
        } else if (unmarshalledObject instanceof DataObject) {
            String localName = ((SDOType)((DataObject)unmarshalledObject).getType()).getXmlDescriptor().getDefaultRootElement();
            if (localName == null) {
                localName = ((SDOType)((DataObject)unmarshalledObject).getType()).getXsdLocalName();
            }
            return createDocument((DataObject)unmarshalledObject, ((DataObject)unmarshalledObject).getType().getURI(), localName);
        } else if (unmarshalledObject instanceof XMLDocument) {
            return (XMLDocument)unmarshalledObject;
        }

        return null;
    }

    /**
     * Creates and returns an XMLDocument from the inputReader.
     * The InputStream will be closed after reading.
     * By default does not perform XSD validation.
     * @param inputReader specifies the Reader to read from
     * @param locationURI specifies the URI of the document for relative schema locations
     * @param options implementation-specific options.
     * @return the new XMLDocument loaded
     * @throws IOException for stream exceptions.
     * @throws RuntimeException for errors in XML parsing or
     *    implementation-specific validation.
     */
    public XMLDocument load(Reader inputReader, String locationURI, Object options) throws IOException {
        InputSource inputSource = new InputSource(inputReader);
        return load(inputSource, locationURI, options);
    }

    public XMLDocument load(Source source, String locationURI, Object options) throws IOException {
        // get XMLUnmarshaller once - as we may create a new instance if this helper isDirty=true
        XMLUnmarshaller anXMLUnmarshaller = getXmlUnmarshaller(options);
        Object unmarshalledObject = null;
        if (options == null) {
            try {
                unmarshalledObject = anXMLUnmarshaller.unmarshal(source);
            } catch(XMLMarshalException xmlException){
                handleXMLMarshalException(xmlException);
            }
        } else {
            try {
                DataObject optionsDataObject = (DataObject)options;
                try {
                    SDOType theType = (SDOType)optionsDataObject.get(SDOConstants.TYPE_LOAD_OPTION);
                    try{
                        if (theType != null) {
                            if(theType.isDataType()) {
                                theType = (SDOType)((SDOTypeHelper)this.aHelperContext.getTypeHelper()).getWrappersHashMap().get(theType.getQName());
                            }
                            if(theType != null) {
                                unmarshalledObject = anXMLUnmarshaller.unmarshal(source, theType.getImplClass());
                            } else {
                                unmarshalledObject = anXMLUnmarshaller.unmarshal(source);
                            }
                        }else{
                            unmarshalledObject = anXMLUnmarshaller.unmarshal(source);
                        }
                    } catch(XMLMarshalException xmlException){
                        handleXMLMarshalException(xmlException);
                    }
                } catch (ClassCastException ccException) {
                  throw SDOException.typePropertyMustBeAType(ccException);
                }
            } catch (ClassCastException ccException) {
               throw SDOException.optionsMustBeADataObject(ccException, SDOConstants.ORACLE_SDO_URL ,SDOConstants.XMLHELPER_LOAD_OPTIONS);
            }

        }

        if (unmarshalledObject instanceof XMLRoot) {
            XMLRoot xmlRoot = (XMLRoot)unmarshalledObject;
            XMLDocument xmlDocument = createDocument((DataObject)((XMLRoot)unmarshalledObject).getObject(), ((XMLRoot)unmarshalledObject).getNamespaceURI(), ((XMLRoot)unmarshalledObject).getLocalName());
            if(xmlRoot.getEncoding() != null) {
                xmlDocument.setEncoding(xmlRoot.getEncoding());
            }
            if(xmlRoot.getXMLVersion() != null) {
                xmlDocument.setXMLVersion(xmlRoot.getXMLVersion());
            }
            xmlDocument.setSchemaLocation(xmlRoot.getSchemaLocation());
            xmlDocument.setNoNamespaceSchemaLocation(xmlRoot.getNoNamespaceSchemaLocation());
            return xmlDocument;
        } else if (unmarshalledObject instanceof DataObject) {
            DataObject unmarshalledDataObject = (DataObject)unmarshalledObject;
            String localName = ((SDOType)((DataObject)unmarshalledObject).getType()).getXmlDescriptor().getDefaultRootElement();
            if (localName == null) {
                localName = ((SDOType)((DataObject)unmarshalledObject).getType()).getXsdLocalName();
            }
            return createDocument(unmarshalledDataObject, unmarshalledDataObject.getType().getURI(), localName);
        } else if (unmarshalledObject instanceof XMLDocument) {
            return (XMLDocument)unmarshalledObject;
        }
        return null;
    }

    /**
     * Returns the DataObject saved as an XML document with the specified root element.
     * Same as
     *   StringWriter stringWriter = new StringWriter();
     *   save(createDocument(dataObject, rootElementURI, rootElementName),
     *     stringWriter, null);
     *   stringWriter.toString();
     *
     * @param dataObject specifies DataObject to be saved
     * @param rootElementURI the Target Namespace URI of the root XML element
     * @param rootElementName the Name of the root XML element
     * @return the saved XML document as a string
     * @throws IllegalArgumentException if the dataObject tree
     *    is not closed or has no container.
     */
    public String save(DataObject dataObject, String rootElementURI, String rootElementName) {
        try {
            StringWriter writer = new StringWriter();
            save(dataObject, rootElementURI, rootElementName, writer, getXmlMarshaller(null));
            return writer.toString();
        } catch (XMLMarshalException e) {
            throw SDOException.xmlMarshalExceptionOccurred(e, rootElementURI, rootElementName);
        }
    }

    /**
     * Saves the DataObject as an XML document with the specified root element.
     * Same as
     *   save(createDocument(dataObject, rootElementURI, rootElementName),
     *     outputStream, null);
     *
     * @param dataObject specifies DataObject to be saved
     * @param rootElementURI the Target Namespace URI of the root XML element
     * @param rootElementName the Name of the root XML element
     * @param outputStream specifies the OutputStream to write to.
     * @throws IOException for stream exceptions.
     * @throws IllegalArgumentException if the dataObject tree
     *    is not closed or has no container.
     */
    public void save(DataObject dataObject, String rootElementURI, String rootElementName, OutputStream outputStream) throws XMLMarshalException, IOException {
        XMLMarshaller xmlMarshaller = getXmlMarshaller(null);
        OutputStreamWriter writer = new OutputStreamWriter(outputStream, xmlMarshaller.getEncoding());
        save(dataObject, rootElementURI, rootElementName, writer, xmlMarshaller);
    }

    public void serialize(XMLDocument xmlDocument, OutputStream outputStream, Object options) throws IOException {
        XMLMarshaller xmlMarshaller = getXmlMarshaller();
        XMLAttachmentMarshaller attachmentMarshaller = xmlMarshaller.getAttachmentMarshaller();
        //temporarily null out the attachment marshaller as it should not be used during serialization
        xmlMarshaller.setAttachmentMarshaller(null);
        OutputStreamWriter writer = new OutputStreamWriter(outputStream, xmlMarshaller.getEncoding());
        save(xmlDocument, writer, xmlMarshaller);
        xmlMarshaller.setAttachmentMarshaller(attachmentMarshaller);
    }

    /**
     * Serializes an XMLDocument as an XML document into the outputStream.
     * If the DataObject's Type was defined by an XSD, the serialization
     *   will follow the XSD.
     * Otherwise the serialization will follow the format as if an XSD
     *   were generated as defined by the SDO specification.
     * The OutputStream will be flushed after writing.
     * Does not perform validation to ensure compliance with an XSD.
     * @param xmlDocument specifies XMLDocument to be saved
     * @param outputStream specifies the OutputStream to write to.
     * @param options implementation-specific options.
     * @throws IOException for stream exceptions.
     * @throws IllegalArgumentException if the dataObject tree
     *    is not closed or has no container.
     */
    public void save(XMLDocument xmlDocument, OutputStream outputStream, Object options) throws IOException {
        if (xmlDocument == null) {
            throw new IllegalArgumentException(SDOException.cannotPerformOperationWithNullInputParameter("save", "xmlDocument"));
        }
        XMLMarshaller xmlMarshaller = getXmlMarshaller(options);
        String encoding = xmlMarshaller.getEncoding();
        if(xmlDocument.getEncoding() != null) {
            encoding = xmlDocument.getEncoding();
        }
        OutputStreamWriter writer = new OutputStreamWriter(outputStream, encoding);
        save(xmlDocument, writer, xmlMarshaller);
    }

    /**
     * Serializes an XMLDocument as an XML document into the outputWriter.
     * If the DataObject's Type was defined by an XSD, the serialization
     *   will follow the XSD.
     * Otherwise the serialization will follow the format as if an XSD
     *   were generated as defined by the SDO specification.
     * The OutputStream will be flushed after writing.
     * Does not perform validation to ensure compliance with an XSD.
     * @param xmlDocument specifies XMLDocument to be saved
     * @param outputWriter specifies the Writer to write to.
     * @param options implementation-specific options.
     * @throws IOException for stream exceptions.
     * @throws IllegalArgumentException if the dataObject tree
     *    is not closed or has no container.
     */
    public void save(XMLDocument xmlDocument, Writer outputWriter, Object options) throws IOException {
        save(xmlDocument, outputWriter, getXmlMarshaller(options));
    }

    private void save(XMLDocument xmlDocument, Writer outputWriter, XMLMarshaller anXMLMarshaller) throws IOException {
        if (xmlDocument == null) {
            throw new IllegalArgumentException(SDOException.cannotPerformOperationWithNullInputParameter("save", "xmlDocument"));
        }

        // Ask the SDOXMLDocument if we should include the XML declaration in the resulting XML
        anXMLMarshaller.setFragment(!xmlDocument.isXMLDeclaration());

        anXMLMarshaller.setEncoding(xmlDocument.getEncoding());
        anXMLMarshaller.setSchemaLocation(xmlDocument.getSchemaLocation());
        anXMLMarshaller.setNoNamespaceSchemaLocation(xmlDocument.getNoNamespaceSchemaLocation());

        WriterRecord writerRecord;
        if(anXMLMarshaller.isFormattedOutput()) {
            writerRecord = new FormattedWriterRecord();
        } else {
            writerRecord = new WriterRecord();
        }
        writerRecord.setWriter(outputWriter);
        writerRecord.setMarshaller(anXMLMarshaller);

        SDOMarshalListener listener = ((SDOMarshalListener)anXMLMarshaller.getMarshalListener());

        listener.setMarshalledObject(xmlDocument.getRootObject());
        listener.setMarshalledObjectRootQName(new QName(xmlDocument.getRootElementURI(), xmlDocument.getRootElementName()));
        listener.setRootMarshalRecord(writerRecord);

        try{
            anXMLMarshaller.marshal(xmlDocument, writerRecord);
            writerRecord.flush();
        }catch(XMLMarshalException xme){
            if(xme.getErrorCode() == XMLMarshalException.DESCRIPTOR_NOT_FOUND_IN_PROJECT){
            if(aHelperContext != ((SDOType)xmlDocument.getRootObject().getType()).getHelperContext()){
                 throw SDOException.dataObjectNotFromHelperContext();
           }
            }
        } finally{
            listener.setMarshalledObject(null);
            listener.setMarshalledObjectRootQName(null);
            listener.setRootMarshalRecord(null);
        }
        outputWriter.flush();
    }

    public void save(XMLDocument xmlDocument, Result result, Object options) throws IOException {
        if (xmlDocument == null) {
            throw new IllegalArgumentException(SDOException.cannotPerformOperationWithNullInputParameter("save", "xmlDocument"));
        }

        if (result instanceof StreamResult) {
            StreamResult streamResult = (StreamResult)result;
            Writer writer = streamResult.getWriter();
            if (null == writer) {
                save(xmlDocument, streamResult.getOutputStream(), options);
            } else {
                save(xmlDocument, writer, options);
            }

        } else {
            // get XMLMarshaller once - as we may create a new instance if this helper isDirty=true
            XMLMarshaller anXMLMarshaller = getXmlMarshaller(options);

            // Ask the SDOXMLDocument if we should include the XML declaration in the resulting XML
            anXMLMarshaller.setFragment(!xmlDocument.isXMLDeclaration());

            anXMLMarshaller.setEncoding(xmlDocument.getEncoding());
            anXMLMarshaller.setSchemaLocation(xmlDocument.getSchemaLocation());
            anXMLMarshaller.setNoNamespaceSchemaLocation(xmlDocument.getNoNamespaceSchemaLocation());

            SDOMarshalListener listener = ((SDOMarshalListener)anXMLMarshaller.getMarshalListener());

            listener.setMarshalledObject(xmlDocument.getRootObject());
            listener.setMarshalledObjectRootQName(new QName(xmlDocument.getRootElementURI(), xmlDocument.getRootElementName()));

            if(result instanceof SAXResult) {
                ContentHandlerRecord marshalRecord = new ContentHandlerRecord();
                marshalRecord.setContentHandler(((SAXResult)result).getHandler());
                marshalRecord.setMarshaller(anXMLMarshaller);
                listener.setRootMarshalRecord(marshalRecord);
                anXMLMarshaller.marshal(xmlDocument, marshalRecord);
                marshalRecord.flush();
            } else if(result instanceof DOMResult) {
                NodeRecord marshalRecord = new NodeRecord();
                marshalRecord.setDOM(((DOMResult)result).getNode());
                marshalRecord.setMarshaller(anXMLMarshaller);
                listener.setRootMarshalRecord(marshalRecord);
                anXMLMarshaller.marshal(xmlDocument, marshalRecord);
                marshalRecord.flush();
            } else {
                StringWriter writer = new StringWriter();
                this.save(xmlDocument, writer, options);
                String xml = writer.toString();
                StreamSource source = new StreamSource(new java.io.StringReader(xml));
                anXMLMarshaller.getTransformer().transform(source, result);
            }
            listener.setMarshalledObject(null);
            listener.setMarshalledObjectRootQName(null);
            listener.setRootMarshalRecord(null);
        }

    }

    /**
     * Creates an XMLDocument with the specified XML rootElement for the DataObject.
     * @param dataObject specifies DataObject to be saved
     * @param rootElementURI the Target Namespace URI of the root XML element
     * @param rootElementName the Name of the root XML element
     * @return XMLDocument a new XMLDocument set with the specified parameters.
     */
    public XMLDocument createDocument(DataObject dataObject, String rootElementURI, String rootElementName) {
        SDOXMLDocument document = new SDOXMLDocument();
        document.setRootObject(dataObject);
        document.setRootElementURI(rootElementURI);
        if (rootElementName != null) {
            document.setRootElementName(rootElementName);
        }

        Property globalProp = getHelperContext().getXSDHelper().getGlobalProperty(rootElementURI, rootElementName, true);
        if (null != globalProp) {
            document.setSchemaType(((SDOType) globalProp.getType()).getXsdType());
        }

        document.setEncoding(SDOXMLDocument.DEFAULT_XML_ENCODING);
        document.setXMLVersion(SDOXMLDocument.DEFAULT_XML_VERSION);

        return document;
    }

    /**
         * INTERNAL:
         * Saves the DataObject as an XML document with the specified root element.
         * Same as
         *   save(createDocument(dataObject, rootElementURI, rootElementName),
         *     writer, null);
         *
         * @param dataObject specifies DataObject to be saved
         * @param rootElementURI the Target Namespace URI of the root XML element
         * @param rootElementName the Name of the root XML element
         * @param writer specifies the Writer to write to.
         * @throws IOException for stream exceptions.
         * @throws IllegalArgumentException if the dataObject tree
         *    is not closed or has no container.
         */
    private void save(DataObject rootObject, String rootElementURI, String rootElementName, Writer writer, XMLMarshaller anXMLMarshaller) throws XMLMarshalException {
        SDOXMLDocument xmlDocument = (SDOXMLDocument)createDocument(rootObject, rootElementURI, rootElementName);

        // Ask the SDOXMLDocument if we should include the XML declaration in the resulting XML
        anXMLMarshaller.setFragment(!xmlDocument.isXMLDeclaration());
        WriterRecord writerRecord;
        if(anXMLMarshaller.isFormattedOutput()) {
            writerRecord = new FormattedWriterRecord();
        } else {
            writerRecord = new WriterRecord();
        }
        writerRecord.setWriter(writer);
        writerRecord.setMarshaller(anXMLMarshaller);

        SDOMarshalListener listener = ((SDOMarshalListener)anXMLMarshaller.getMarshalListener());

        listener.setMarshalledObject(rootObject);
        listener.setMarshalledObjectRootQName(new QName(rootElementURI, rootElementName));
        listener.setRootMarshalRecord(writerRecord);

        try{
            anXMLMarshaller.marshal(xmlDocument, writerRecord);
            writerRecord.flush();
        }catch(XMLMarshalException xme){
            if(xme.getErrorCode() == XMLMarshalException.DESCRIPTOR_NOT_FOUND_IN_PROJECT){
            if(aHelperContext != ((SDOType)rootObject.getType()).getHelperContext()){
                 throw SDOException.dataObjectNotFromHelperContext();
           }
            }
        }finally{
            listener.setMarshalledObject(null);
            listener.setMarshalledObjectRootQName(null);
            listener.setRootMarshalRecord(null);
        }


        try {
            writer.flush();
        } catch(IOException ex) {
            throw XMLMarshalException.marshalException(ex);
        }
    }

    public void setLoader(SDOClassLoader loader) {
        this.loader = loader;
        getXmlConversionManager().setLoader(this.loader);
    }

    public SDOClassLoader getLoader() {
        return loader;
    }

    public void setXmlContext(XMLContext xmlContext) {
        this.xmlContext = xmlContext;
    }

    public synchronized XMLContext getXmlContext() {
        if (xmlContext == null) {
            xmlContext = new XMLContext(getTopLinkProject());
            XMLConversionManager xmlConversionManager = getXmlConversionManager();
            xmlConversionManager.setLoader(this.loader);
            xmlConversionManager.setTimeZone(TimeZone.getTimeZone("GMT"));
            xmlConversionManager.setTimeZoneQualified(true);
        }
        return xmlContext;
    }

    public void initializeDescriptor(XMLDescriptor descriptor){
        AbstractSession theSession = (AbstractSession)getXmlContext().getSession();

        //do initialization for new descriptor;
        descriptor.preInitialize(theSession);
        descriptor.initialize(theSession);
        descriptor.postInitialize(theSession);

        descriptor.getObjectBuilder().initializePrimaryKey(theSession);
        getXmlContext().storeXMLDescriptorByQName(descriptor);
    }

    public void addDescriptors(List types) {
        for (int i = 0; i < types.size(); i++) {
            SDOType nextType = (SDOType)types.get(i);

            if (!nextType.isDataType() && nextType.isFinalized()){
              XMLDescriptor nextDescriptor = nextType.getXmlDescriptor();
              getTopLinkProject().addDescriptor(nextDescriptor);
            }
        }
        for (int i = 0; i < types.size(); i++) {
          SDOType nextType = (SDOType)types.get(i);
          if (!nextType.isDataType() && nextType.isFinalized()){
             XMLDescriptor nextDescriptor = nextType.getXmlDescriptor();
             initializeDescriptor(nextDescriptor);
          }
        }
    }

    public void setTopLinkProject(Project toplinkProject) {
        this.topLinkProject = toplinkProject;
        this.xmlContext = null;
        this.xmlMarshallerMap.clear();
        this.xmlUnmarshallerMap.clear();
    }

    public Project getTopLinkProject() {
        if (topLinkProject == null) {
            topLinkProject = new Project();
            XMLLogin xmlLogin = new XMLLogin();
            xmlLogin.setEqualNamespaceResolvers(false);
            topLinkProject.setDatasourceLogin(xmlLogin);
            // 200606_changeSummary
            NamespaceResolver nr = new NamespaceResolver();
            SDOTypeHelper sdoTypeHelper = (SDOTypeHelper) aHelperContext.getTypeHelper();
            String sdoPrefix = sdoTypeHelper.getPrefix(SDOConstants.SDO_URL);
            nr.put(sdoPrefix, SDOConstants.SDO_URL);
            SDOType changeSummaryType = (SDOType) sdoTypeHelper.getType(SDOConstants.SDO_URL, SDOConstants.CHANGESUMMARY);
            changeSummaryType.getXmlDescriptor().setNamespaceResolver(nr);
            topLinkProject.addDescriptor(changeSummaryType.getXmlDescriptor());
            SDOType openSequencedType = (SDOType) aHelperContext.getTypeHelper().getType(SDOConstants.ORACLE_SDO_URL, "OpenSequencedType");
            topLinkProject.addDescriptor(openSequencedType.getXmlDescriptor());
            SDOTypeType typeType = (SDOTypeType)aHelperContext.getTypeHelper().getType(SDOConstants.SDO_URL, SDOConstants.TYPE);
            typeType.getXmlDescriptor().setNamespaceResolver(nr);
            if(!typeType.isInitialized()) {
                typeType.initializeMappings();
            }
            topLinkProject.addDescriptor(typeType.getXmlDescriptor());
            SDOPropertyType propertyType = (SDOPropertyType)aHelperContext.getTypeHelper().getType(SDOConstants.SDO_URL, SDOConstants.PROPERTY);
            if(!propertyType.isInitialized()) {
                propertyType.initializeMappings();
            }
            topLinkProject.addDescriptor(propertyType.getXmlDescriptor());

            ((SDOTypeHelper)aHelperContext.getTypeHelper()).addWrappersToProject(topLinkProject);
        }
        return topLinkProject;
    }

    public void setXmlMarshaller(XMLMarshaller xmlMarshaller) {
        this.xmlMarshallerMap.put(Thread.currentThread(), xmlMarshaller);
    }

    public XMLMarshaller getXmlMarshaller() {
        XMLMarshaller marshaller = xmlMarshallerMap.get(Thread.currentThread());

        if (marshaller == null) {
            marshaller = getXmlContext().createMarshaller();
            marshaller.setMarshalListener(new SDOMarshalListener(marshaller, (SDOTypeHelper) aHelperContext.getTypeHelper()));
            xmlMarshallerMap.put(Thread.currentThread(), marshaller);
        }

        XMLContext context = getXmlContext();
        if (marshaller.getXMLContext() != context) {
            marshaller.setXMLContext(context);
        }
        return marshaller;
    }

    private XMLMarshaller getXmlMarshaller(Object options) {
        XMLMarshaller xmlMarshaller = getXmlMarshaller().clone();
        if(null == options) {
            return xmlMarshaller;
        }
        try {
            DataObject optionsDO = (DataObject) options;
            if(optionsDO.isSet(SDOConstants.ATTACHMENT_MARSHALLER_OPTION)) {
                xmlMarshaller.setAttachmentMarshaller((XMLAttachmentMarshaller)optionsDO.get(SDOConstants.ATTACHMENT_MARSHALLER_OPTION));
            }
            xmlMarshaller.setMarshalListener(new SDOMarshalListener(xmlMarshaller, (SDOTypeHelper) aHelperContext.getTypeHelper()));
            return xmlMarshaller;
        } catch(ClassCastException ccException) {
            throw SDOException.optionsMustBeADataObject(ccException, SDOConstants.ORACLE_SDO_URL ,SDOConstants.XMLHELPER_LOAD_OPTIONS);
        }
    }

    public void setXmlUnmarshaller(XMLUnmarshaller xmlUnmarshaller) {
        this.xmlUnmarshallerMap.put(Thread.currentThread(), xmlUnmarshaller);
    }

    public XMLUnmarshaller getXmlUnmarshaller() {
        XMLUnmarshaller unmarshaller = xmlUnmarshallerMap.get(Thread.currentThread());

        if (null == unmarshaller) {
            unmarshaller = getXmlContext().createUnmarshaller();
            unmarshaller.getProperties().put(SDOConstants.SDO_HELPER_CONTEXT, aHelperContext);
            unmarshaller.setUnmappedContentHandlerClass(SDOUnmappedContentHandler.class);
            unmarshaller.setUnmarshalListener(new SDOUnmarshalListener(aHelperContext));
            unmarshaller.setResultAlwaysXMLRoot(true);
            xmlUnmarshallerMap.put(Thread.currentThread(), unmarshaller);
        }

        XMLContext context = getXmlContext();
        if (unmarshaller.getXMLContext() != context) {
            unmarshaller.setXMLContext(context);
        }
        return unmarshaller;
    }

    private XMLUnmarshaller getXmlUnmarshaller(Object options) {
        XMLUnmarshaller xmlUnmarshaller = getXmlUnmarshaller().clone();
        if(null == options) {
            return xmlUnmarshaller;
        }
        try {
            DataObject optionsDO = (DataObject) options;
            if(optionsDO.isSet(SDOConstants.ATTACHMENT_UNMARSHALLER_OPTION)) {
                xmlUnmarshaller.setAttachmentUnmarshaller((XMLAttachmentUnmarshaller)optionsDO.get(SDOConstants.ATTACHMENT_UNMARSHALLER_OPTION));
            }
            return xmlUnmarshaller;
        } catch(ClassCastException ccException) {
            throw SDOException.optionsMustBeADataObject(ccException, SDOConstants.ORACLE_SDO_URL ,SDOConstants.XMLHELPER_LOAD_OPTIONS);
        }
    }

    public void reset() {
        setTopLinkProject(null);
        setXmlContext(null);
        this.xmlMarshallerMap.clear();
        this.xmlUnmarshallerMap.clear();
        setLoader(new SDOClassLoader(getClass().getClassLoader(), aHelperContext));
    }

    public HelperContext getHelperContext() {
        return aHelperContext;
    }

    public void setHelperContext(HelperContext helperContext) {
        aHelperContext = helperContext;
    }

    private void handleXMLMarshalException(XMLMarshalException xmlException) throws IOException {
        if(xmlException.getErrorCode() == XMLMarshalException.NO_DESCRIPTOR_WITH_MATCHING_ROOT_ELEMENT || xmlException.getErrorCode() == XMLMarshalException.DESCRIPTOR_NOT_FOUND_IN_PROJECT){
            throw SDOException.globalPropertyNotFound();
        } else if (xmlException.getCause() instanceof IOException) {
            throw (IOException) xmlException.getCause();
        } else{
            throw xmlException;
        }
    }

    public XMLConversionManager getXmlConversionManager() {
        return (XMLConversionManager) getXmlContext().getSession().getDatasourceLogin().getDatasourcePlatform().getConversionManager();
    }

}
