/*******************************************************************************
 * Copyright (c) 1998, 2016 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 v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/
package org.eclipse.persistence.internal.oxm.record;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.ValidatorHandler;

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.helper.XMLHelper;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.Context;
import org.eclipse.persistence.internal.oxm.ConversionManager;
import org.eclipse.persistence.internal.oxm.MediaType;
import org.eclipse.persistence.internal.oxm.Root;
import org.eclipse.persistence.internal.oxm.Unmarshaller;
import org.eclipse.persistence.internal.oxm.UnmarshallerHandler;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.UnmarshalKeepAsElementPolicy;
import org.eclipse.persistence.internal.oxm.record.json.JsonStructureReader;
import org.eclipse.persistence.platform.xml.DefaultErrorHandler;
import org.eclipse.persistence.platform.xml.SAXDocumentBuilder;
import org.eclipse.persistence.platform.xml.XMLParser;
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.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * INTERNAL:
 * <p><b>Purpose:</b>Provide an implementation of PlatformUnmarshaller that makes use of the SAX parser
 * to build Java Objects from SAX Events.
 * <p><b>Responsibilities:</b><ul>
 * <li>Implement the required unmarshal methods from PlatformUnmarshaller</li>
 * <li>Check to see if document preservation is enabled, and if so, always unmarshal from a node</li>
 * </ul>
 * 
 * @author bdoughan
 * @see org.eclipse.persistence.oxm.platform.SAXPlatform
 */
public class SAXUnmarshaller implements PlatformUnmarshaller {
    private static final String VALIDATING = "http://xml.org/sax/features/validation";
    private static final String SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
    private static final String SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
    private static final String XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
    private static final UnmarshalKeepAsElementPolicy KEEP_UNKNOWN_AS_ELEMENT = new UnmarshalKeepAsElementPolicy() {

        @Override
        public boolean isKeepAllAsElement() {
            return false;
        }

        @Override
        public boolean isKeepNoneAsElement() {
            return false;
        }

        @Override
        public boolean isKeepUnknownAsElement() {
            return true;
        }

    };

    private int validationMode = XMLParser.NONVALIDATING;
    private Schema schema;
    private Object[] schemas;
    private EntityResolver entityResolver;
    private ErrorHandler errorHandler = DefaultErrorHandler.getInstance();

    private SAXParser saxParser;
    private XMLReader xmlReader;
    private Unmarshaller xmlUnmarshaller;
    private XMLParser xmlParser;
    private boolean isResultAlwaysXMLRoot, isWhitespacePreserving;
    private SAXParserFactory saxParserFactory;
    private String systemId = null;
    private Map<String, Boolean> parserFeatures;
    private boolean disableSecureProcessing = false;
    private boolean shouldReset = true;
    private XMLPlatform xmlPLatform;

    public SAXUnmarshaller(Unmarshaller xmlUnmarshaller, Map<String, Boolean> parserFeatures) throws XMLMarshalException {
        super();
        this.parserFeatures = parserFeatures;
        try {

            this.xmlUnmarshaller = xmlUnmarshaller;
        } catch (Exception e) {
            throw XMLMarshalException.errorInstantiatingSchemaPlatform(e);
        }
    }

    private SAXParserFactory getSAXParserFactory() throws XMLMarshalException {
        if (null == saxParserFactory || shouldReset) {
            try {
                saxParserFactory = XMLHelper.createParserFactory(isSecureProcessingDisabled());;
                saxParserFactory.setFeature(XMLReader.NAMESPACE_PREFIXES_FEATURE, true);
                try {
                    saxParserFactory.setFeature(XMLReader.REPORT_IGNORED_ELEMENT_CONTENT_WHITESPACE_FEATURE, true);
                } catch (org.xml.sax.SAXNotRecognizedException ex) {
                    // ignore if the parser doesn't recognize or support this feature
                } catch (org.xml.sax.SAXNotSupportedException ex) {
                }

                if (null != parserFeatures) {
                    for (Map.Entry<String, Boolean> parserFeature : parserFeatures.entrySet()) {
                        try {
                            saxParserFactory.setFeature(parserFeature.getKey(), parserFeature.getValue());
                        } catch (org.xml.sax.SAXNotRecognizedException ex) {
                            // ignore if the parser doesn't recognize or support this feature
                        } catch (org.xml.sax.SAXNotSupportedException ex) {
                        }
                    }
                }
                return saxParserFactory;
            } catch (Exception e) {
                throw XMLMarshalException.errorInstantiatingSchemaPlatform(e);
            }
        }
        return saxParserFactory;
    }

    private SAXParser getSAXParser() {
        if (null == saxParser) {
            try {
                saxParser = getSAXParserFactory().newSAXParser();
            } catch (Exception e) {
                throw XMLMarshalException.errorInstantiatingSchemaPlatform(e);
            }
        }
        return saxParser;
    }

    private XMLParser getXMLParser() {
        if (xmlParser == null || shouldReset) {
            XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
            xmlPlatform.setDisableSecureProcessing(isSecureProcessingDisabled());
            xmlParser = xmlPlatform.newXMLParser();
            xmlParser.setNamespaceAware(true);
            if (null != errorHandler) {
                xmlParser.setErrorHandler(errorHandler);
            }
            if (null != entityResolver) {
                xmlParser.setEntityResolver(entityResolver);
            }
            xmlParser.setValidationMode(validationMode);
            xmlParser.setWhitespacePreserving(isWhitespacePreserving);
            shouldReset = false;
        }
        return xmlParser;
    }

    private XMLReader getXMLReader() {
        return getXMLReader(null);
    }

    private XMLReader getXMLReader(Class clazz) {
        if (null == xmlReader) {
            xmlReader = getNewXMLReader(clazz, xmlUnmarshaller.getMediaType());
        }
        return xmlReader;
    }

    private XMLReader getNewXMLReader(MediaType mediaType) {
        return getNewXMLReader(null, mediaType);
    }

    private XMLReader getNewXMLReader(Class clazz, MediaType mediaType) {

        if (null != mediaType && mediaType.isApplicationJSON()) {
            return new JsonStructureReader(xmlUnmarshaller, clazz);
        }
        try {
            XMLReader xmlReader = new XMLReader(getSAXParser().getXMLReader());
            if (null != errorHandler) {
                xmlReader.setErrorHandler(errorHandler);
            }
            if (null != entityResolver) {
                xmlReader.setEntityResolver(entityResolver);
            }
            setValidationMode(xmlReader, getValidationMode());
            if (null != getSchema()) {
                xmlReader.setFeature(VALIDATING, xmlReader.getFeature(VALIDATING));
            }
            return xmlReader;
        } catch (Exception e) {
            throw XMLMarshalException.errorInstantiatingSchemaPlatform(e);
        }
    }

    @Override
    public EntityResolver getEntityResolver() {
        return entityResolver;
    }

    @Override
    public void setEntityResolver(EntityResolver entityResolver) {
        if (null != xmlReader) {
            xmlReader.setEntityResolver(entityResolver);
        }
        if (null != xmlParser) {
            xmlParser.setEntityResolver(entityResolver);
        }
        this.entityResolver = entityResolver;
    }

    @Override
    public ErrorHandler getErrorHandler() {
        return errorHandler;
    }

    @Override
    public void setErrorHandler(ErrorHandler errorHandler) {
        if (null != xmlReader) {
            xmlReader.setErrorHandler(errorHandler);
        }
        if (null != xmlParser) {
            xmlParser.setErrorHandler(errorHandler);
        }
        this.errorHandler = errorHandler;
    }

    @Override
    public int getValidationMode() {
        return validationMode;
    }

    @Override
    public void setValidationMode(int validationMode) {
        setValidationMode(xmlReader, validationMode);
    }

    public void setValidationMode(XMLReader xmlReader, int validationMode) {
        try {
            this.validationMode = validationMode;
            if (null != xmlParser) {
                xmlParser.setValidationMode(validationMode);
            }
            if (null == xmlReader) {
                return;
            }
            switch (validationMode) {
            case XMLParser.NONVALIDATING: {
                xmlReader.setFeature(VALIDATING, false);
                break;
            }
            case XMLParser.DTD_VALIDATION: {
                xmlReader.setFeature(VALIDATING, true);
                XMLHelper.allowExternalDTDAccess(xmlReader, "all", false);
                break;
            }
            case XMLParser.SCHEMA_VALIDATION: {
                try {
                    xmlReader.setFeature(VALIDATING, true);
                    XMLHelper.allowExternalAccess(xmlReader, "all", false);
                    saxParser.setProperty(SCHEMA_LANGUAGE, XML_SCHEMA);
                    saxParser.setProperty(SCHEMA_SOURCE, schemas);
                } catch (Exception e) {
                    xmlReader.setFeature(VALIDATING, false);
                }
                break;
            }
            }
        } catch (Exception e) {
            // Don't change the validation mode.
        }
    }

    @Override
    public void setWhitespacePreserving(boolean isWhitespacePreserving) {
        this.isWhitespacePreserving = isWhitespacePreserving;
        if (null != xmlParser) {
            xmlParser.setWhitespacePreserving(isWhitespacePreserving);
        }
    }

    @Override
    public void setSchemas(Object[] schemas) {
        this.schemas = schemas;
    }

    @Override
    public void setSchema(Schema schema) {
        this.schema = schema;
        if (null != xmlParser) {
            xmlParser.setXMLSchema(schema);
        }
    }

    @Override
    public Schema getSchema() {
        return schema;
    }

    @Override
    public Object unmarshal(File file) {
        try {
            if (xmlUnmarshaller.getContext().hasDocumentPreservation()) {
                Node domElement = getXMLParser().parse(file).getDocumentElement();
                return unmarshal(domElement);
            }

            this.systemId = file.toURI().toURL().toExternalForm();

            FileInputStream inputStream = new FileInputStream(file);
            try {
                return unmarshal(inputStream);
            } finally {
                inputStream.close();
            }
        } catch (FileNotFoundException e) {
            throw XMLMarshalException.unmarshalException(e);
        } catch (IOException e) {
            throw XMLMarshalException.unmarshalException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }
    }

    @Override
    public Object unmarshal(File file, Class clazz) {
        try {
            if (xmlUnmarshaller.getContext().hasDocumentPreservation()) {
                Node domElement = getXMLParser().parse(file).getDocumentElement();
                return unmarshal(domElement, clazz);
            }

            this.systemId = file.toURI().toURL().toExternalForm();

            FileInputStream inputStream = new FileInputStream(file);
            try {
                return unmarshal(inputStream, clazz);
            } finally {
                inputStream.close();
            }
        } catch (FileNotFoundException e) {
            throw XMLMarshalException.unmarshalException(e);
        } catch (IOException e) {
            throw XMLMarshalException.unmarshalException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }
    }

    @Override
    public Object unmarshal(InputStream inputStream) {
        if (xmlUnmarshaller.getContext().hasDocumentPreservation()) {
            Node domElement = getXMLParser().parse(inputStream).getDocumentElement();
            return unmarshal(domElement);
        }
        InputSource inputSource = new InputSource(inputStream);
        return unmarshal(inputSource);
    }

    @Override
    public Object unmarshal(InputStream inputStream, Class clazz) {
        if (xmlUnmarshaller.getContext().hasDocumentPreservation()) {
            Node domElement = getXMLParser().parse(inputStream).getDocumentElement();
            return unmarshal(domElement, clazz);
        }
        InputSource inputSource = new InputSource(inputStream);
        return unmarshal(inputSource, clazz);
    }

    @Override
    public Object unmarshal(InputSource inputSource) {
        if (inputSource != null && null == inputSource.getSystemId()) {
            inputSource.setSystemId(this.systemId);
        }

        if (xmlUnmarshaller.isAutoDetectMediaType()) {
            BufferedReader bufferedReader = getBufferedReaderForInputSource(inputSource);
            MediaType mediaType = getMediaType(bufferedReader);
            return unmarshal(getNewXMLReader(mediaType), new InputSource(bufferedReader));
        }
        return unmarshal(getXMLReader(), inputSource);
    }

    public Object unmarshal(InputSource inputSource, XMLReader xmlReader) {
        try {
            if (inputSource != null && null == inputSource.getSystemId()) {
                inputSource.setSystemId(this.systemId);
            }

            SAXUnmarshallerHandler saxUnmarshallerHandler = new SAXUnmarshallerHandler(xmlUnmarshaller.getContext());
            saxUnmarshallerHandler.setXMLReader(xmlReader);
            saxUnmarshallerHandler.setUnmarshaller(xmlUnmarshaller);
            setContentHandler(xmlReader, saxUnmarshallerHandler);
            xmlReader.parse(inputSource);

            // resolve any mapping references
            saxUnmarshallerHandler.resolveReferences();
            return saxUnmarshallerHandler.getObject();
        } catch (IOException e) {
            throw XMLMarshalException.unmarshalException(e);
        } catch (SAXException e) {
            throw convertSAXException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }
    }

    @Override
    public Object unmarshal(InputSource inputSource, Class clazz) {
        if (inputSource != null && null == inputSource.getSystemId()) {
            inputSource.setSystemId(this.systemId);
        }

        if (xmlUnmarshaller.isAutoDetectMediaType()) {
            BufferedReader bufferedReader = getBufferedReaderForInputSource(inputSource);
            MediaType mediaType = getMediaType(bufferedReader);
            return unmarshal(getNewXMLReader(clazz, mediaType), new InputSource(bufferedReader), clazz);
        }
        return unmarshal(getXMLReader(clazz), inputSource, clazz);
    }

    public Object unmarshal(InputSource inputSource, Class clazz, XMLReader xmlReader) {
        if (inputSource != null && null == inputSource.getSystemId()) {
            inputSource.setSystemId(this.systemId);
        }

        UnmarshalRecord unmarshalRecord = null;
        Descriptor xmlDescriptor = null;

        // for XMLObjectReferenceMappings we need a non-shared cache, so
        // try and get a Unit Of Work from the XMLContext
        CoreAbstractSession session = null;

        // check for case where the reference class is a primitive wrapper - in this case, we
        // need to use the conversion manager to convert the node's value to the primitive
        // wrapper class, then create, populate and return an XMLRoot. This will be done
        // via XMLRootRecord.
        boolean isPrimitiveWrapper = false;
        if (clazz == CoreClassConstants.OBJECT) {
            try {
                SAXUnmarshallerHandler saxUnmarshallerHandler = new SAXUnmarshallerHandler(xmlUnmarshaller.getContext());
                saxUnmarshallerHandler.setXMLReader(xmlReader);
                saxUnmarshallerHandler.setUnmarshaller(xmlUnmarshaller);
                saxUnmarshallerHandler.setKeepAsElementPolicy(KEEP_UNKNOWN_AS_ELEMENT);
                setContentHandler(xmlReader, saxUnmarshallerHandler);
                xmlReader.parse(inputSource);

                // resolve any mapping references
                saxUnmarshallerHandler.resolveReferences();
                return saxUnmarshallerHandler.getObject();
            } catch (IOException e) {
                throw XMLMarshalException.unmarshalException(e);
            } catch (SAXException e) {
                throw convertSAXException(e);
            }
        } else {
            // for XMLObjectReferenceMappings we need a non-shared cache, so
            // try and get a Unit Of Work from the XMLContext
            try {
                session = xmlUnmarshaller.getContext().getSession(clazz);
                xmlDescriptor = (Descriptor) session.getDescriptor(clazz);
                unmarshalRecord = xmlUnmarshaller.createUnmarshalRecord(xmlDescriptor, session);

            } catch (XMLMarshalException xme) {
                if (xme.getErrorCode() == XMLMarshalException.DESCRIPTOR_NOT_FOUND_IN_PROJECT) {
                    isPrimitiveWrapper = isPrimitiveWrapper(clazz);
                    if (isPrimitiveWrapper) {
                        unmarshalRecord = xmlUnmarshaller.createRootUnmarshalRecord(clazz);
                    } else {
                        throw xme;
                    }

                } else {
                    throw xme;
                }

            }
        }

        try {
            unmarshalRecord.setXMLReader(xmlReader);
            unmarshalRecord.setUnmarshaller(xmlUnmarshaller);
            setContentHandler(xmlReader, unmarshalRecord);
            xmlReader.setLexicalHandler(unmarshalRecord);
            xmlReader.parse(inputSource);
        } catch (IOException e) {
            throw XMLMarshalException.unmarshalException(e);
        } catch (SAXException e) {
            throw convertSAXException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }

        // resolve mapping references
        unmarshalRecord.resolveReferences(session, xmlUnmarshaller.getIDResolver());

        if (isPrimitiveWrapper) {
            return unmarshalRecord.getCurrentObject();
        }
        return xmlDescriptor.wrapObjectInXMLRoot(unmarshalRecord, this.isResultAlwaysXMLRoot);
    }

    @Override
    public Object unmarshal(Node node) {
        DOMReader reader = new DOMReader(xmlUnmarshaller);
        return unmarshal(reader, node);
    }

    public Object unmarshal(DOMReader reader, Node node) {
        try {
            SAXUnmarshallerHandler handler = new SAXUnmarshallerHandler(xmlUnmarshaller.getContext());
            setContentHandler(reader, handler);
            handler.setXMLReader(reader);
            handler.setUnmarshaller(xmlUnmarshaller);
            reader.parse(node);

            handler.resolveReferences();
            return handler.getObject();
        } catch (SAXException e) {
            throw convertSAXException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }

    }

    @Override
    public Object unmarshal(Node node, Class clazz) {
        DOMReader reader = new DOMReader(xmlUnmarshaller);
        return unmarshal(reader, node, clazz);
    }

    public Object unmarshal(DOMReader domReader, Node node, Class clazz) {
        UnmarshalRecord unmarshalRecord = null;
        Descriptor xmlDescriptor = null;

        CoreAbstractSession session = null;

        // check for case where the reference class is a primitive wrapper - in this case, we
        // need to use the conversion manager to convert the node's value to the primitive
        // wrapper class, then create, populate and return an XMLRoot.  This will be done
        // via XMLRootRecord.
        boolean isPrimitiveWrapper = false;
        if (clazz == CoreClassConstants.OBJECT) {
            SAXUnmarshallerHandler saxUnmarshallerHandler = new SAXUnmarshallerHandler(xmlUnmarshaller.getContext());
            saxUnmarshallerHandler.setXMLReader(domReader);
            saxUnmarshallerHandler.setUnmarshaller(xmlUnmarshaller);
            saxUnmarshallerHandler.setKeepAsElementPolicy(KEEP_UNKNOWN_AS_ELEMENT);
            setContentHandler(domReader, saxUnmarshallerHandler);
            try {
                domReader.parse(node);
            } catch (SAXException e) {
                throw convertSAXException(e);
            }

            // resolve any mapping references
            saxUnmarshallerHandler.resolveReferences();
            return saxUnmarshallerHandler.getObject();
        } else {
            // for XMLObjectReferenceMappings we need a non-shared cache, so
            // try and get a Unit Of Work from the XMLContext
            try {
                session = xmlUnmarshaller.getContext().getSession(clazz);
                xmlDescriptor = (Descriptor) session.getDescriptor(clazz);
                unmarshalRecord = xmlUnmarshaller.createUnmarshalRecord(xmlDescriptor, session);
            } catch (XMLMarshalException xme) {
                if (xme.getErrorCode() == XMLMarshalException.DESCRIPTOR_NOT_FOUND_IN_PROJECT) {
                    isPrimitiveWrapper = isPrimitiveWrapper(clazz);
                    if (isPrimitiveWrapper) {
                        unmarshalRecord = xmlUnmarshaller.createRootUnmarshalRecord(clazz);
                    } else if (Node.class.isAssignableFrom(clazz)) {
                        return createXMLRootForNode(node);
                    } else {
                        throw xme;
                    }

                } else {
                    throw xme;
                }

            }
        }
        try {
            unmarshalRecord.setXMLReader(domReader);
            unmarshalRecord.setUnmarshaller(xmlUnmarshaller);
            setContentHandler(domReader, unmarshalRecord);
            domReader.setLexicalHandler(unmarshalRecord);
            domReader.parse(node);
        } catch (SAXException e) {
            throw convertSAXException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }

        // resolve mapping references
        unmarshalRecord.resolveReferences(session, xmlUnmarshaller.getIDResolver());

        if (isPrimitiveWrapper) {
            return unmarshalRecord.getCurrentObject();
        }
        return xmlDescriptor.wrapObjectInXMLRoot(unmarshalRecord, this.isResultAlwaysXMLRoot);
    }

    private Object createXMLRootForNode(Node node) {
        Root xmlRoot = xmlUnmarshaller.createRoot();
        xmlRoot.setObject(node);
        if (node != null) {
            xmlRoot.setLocalName(node.getLocalName());
            xmlRoot.setNamespaceURI(node.getNamespaceURI());
        }
        return xmlRoot;
    }

    @Override
    public Object unmarshal(Reader reader) {
        if (xmlUnmarshaller.getContext().hasDocumentPreservation()) {
            Node domElement = getXMLParser().parse(reader).getDocumentElement();
            return unmarshal(domElement);
        }
        InputSource inputSource = new InputSource(reader);
        return unmarshal(inputSource);
    }

    @Override
    public Object unmarshal(Reader reader, Class clazz) {
        if (xmlUnmarshaller.getContext().hasDocumentPreservation()) {
            Node domElement = getXMLParser().parse(reader).getDocumentElement();
            return unmarshal(domElement, clazz);
        }
        InputSource inputSource = new InputSource(reader);
        return unmarshal(inputSource, clazz);
    }

    @Override
    public Object unmarshal(Source source) {
        try {
            if (source instanceof SAXSource) {
                SAXSource saxSource = (SAXSource) source;
                XMLReader xmlReader = null;
                if (saxSource.getXMLReader() != null) {
                    if (saxSource.getXMLReader() instanceof XMLReader) {
                        xmlReader = (XMLReader) saxSource.getXMLReader();
                    } else {
                        xmlReader = new XMLReader(saxSource.getXMLReader());
                    }
                    setValidatorHandler(xmlReader);
                }
                if (null == xmlReader) {
                    return unmarshal(saxSource.getInputSource());
                } else {
                    return unmarshal(saxSource.getInputSource(), xmlReader);
                }
            } else if (source instanceof DOMSource) {
                DOMSource domSource = (DOMSource) source;
                return unmarshal(domSource.getNode());
            } else if (source instanceof StreamSource) {
                StreamSource streamSource = (StreamSource) source;
                if (null != streamSource.getReader()) {
                    return unmarshal(streamSource.getReader());
                } else if (null != streamSource.getInputStream()) {
                    return unmarshal(streamSource.getInputStream());
                } else {
                    return unmarshal(streamSource.getSystemId());
                }
            } else if (source instanceof ExtendedSource) {
                ExtendedSource extendedSource = (ExtendedSource) source;
                return unmarshal(null, extendedSource.createReader(xmlUnmarshaller));
            } else {
                UnmarshallerHandler handler = this.xmlUnmarshaller.getUnmarshallerHandler();
                XMLPlatform xmlPlat = XMLPlatformFactory.getInstance().getXMLPlatform();
                xmlPlat.setDisableSecureProcessing(isSecureProcessingDisabled());
                XMLTransformer transformer = xmlPLatform.newXMLTransformer();
                SAXResult result = new SAXResult(handler);
                transformer.transform(source, result);
                return handler.getResult();
            }
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }
    }

    @Override
    public Object unmarshal(Source source, Class clazz) {
        if (source instanceof SAXSource) {
            SAXSource saxSource = (SAXSource) source;
            XMLReader xmlReader = null;
            if (saxSource.getXMLReader() != null) {
                if (saxSource.getXMLReader() instanceof XMLReader) {
                    xmlReader = (XMLReader) saxSource.getXMLReader();
                } else {
                    xmlReader = new XMLReader(saxSource.getXMLReader());
                }
                setValidatorHandler(xmlReader);
            }
            if (null == saxSource.getXMLReader()) {
                return unmarshal(saxSource.getInputSource(), clazz);
            } else {
                return unmarshal(saxSource.getInputSource(), clazz, xmlReader);
            }
        } else if (source instanceof DOMSource) {
            DOMSource domSource = (DOMSource) source;
            return unmarshal(domSource.getNode(), clazz);
        } else if (source instanceof StreamSource) {
            StreamSource streamSource = (StreamSource) source;
            if (null != streamSource.getReader()) {
                return unmarshal(streamSource.getReader(), clazz);
            } else if (null != streamSource.getInputStream()) {
                return unmarshal(streamSource.getInputStream(), clazz);
            } else {
                return unmarshal(streamSource.getSystemId(), clazz);
            }
        } else if (source instanceof ExtendedSource) {
            ExtendedSource extendedSource = (ExtendedSource) source;
            return unmarshal(null, clazz, extendedSource.createReader(xmlUnmarshaller, clazz));
        } else {
            DOMResult result = new DOMResult();
            XMLPlatform xmlPlat = XMLPlatformFactory.getInstance().getXMLPlatform();
            xmlPlat.setDisableSecureProcessing(isSecureProcessingDisabled());
            XMLTransformer transformer = xmlPLatform.newXMLTransformer();
            transformer.transform(source, result);
            return unmarshal(result.getNode(), clazz);

        }
    }

    @Override
    public Object unmarshal(URL url) {
        InputStream inputStream = null;
        try {
            inputStream = url.openStream();
        } catch (Exception e) {
            throw XMLMarshalException.unmarshalException(e);
        }

        this.systemId = url.toExternalForm();

        boolean hasThrownException = false;
        try {
            return unmarshal(inputStream);
        } catch (RuntimeException runtimeException) {
            hasThrownException = true;
            throw runtimeException;
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                if (!hasThrownException) {
                    throw XMLMarshalException.unmarshalException(e);
                }
            }
        }
    }

    @Override
    public Object unmarshal(URL url, Class clazz) {
        InputStream inputStream = null;
        try {
            inputStream = url.openStream();
        } catch (IOException e) {
            throw XMLMarshalException.unmarshalException(e);
        }
        this.systemId = url.toExternalForm();
        try {
            return unmarshal(inputStream, clazz);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                throw XMLMarshalException.unmarshalException(e);
            }
        }
    }

    public Object unmarshal(String systemId) {
        try {
            if (xmlUnmarshaller.isAutoDetectMediaType()) {
                InputSource inputSource = new InputSource(systemId);
                return unmarshal(inputSource);
            }

            XMLReader xmlReader = getXMLReader();
            SAXUnmarshallerHandler saxUnmarshallerHandler = new SAXUnmarshallerHandler(xmlUnmarshaller.getContext());
            saxUnmarshallerHandler.setXMLReader(xmlReader);
            saxUnmarshallerHandler.setUnmarshaller(xmlUnmarshaller);
            setContentHandler(xmlReader, saxUnmarshallerHandler);
            xmlReader.parse(systemId);

            // resolve mapping references
            saxUnmarshallerHandler.resolveReferences();
            return saxUnmarshallerHandler.getObject();
        } catch (IOException e) {
            throw XMLMarshalException.unmarshalException(e);
        } catch (SAXException e) {
            throw convertSAXException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }
    }

    public Object unmarshal(String systemId, Class clazz) {
        if (xmlUnmarshaller.isAutoDetectMediaType()) {
            return unmarshal(new InputSource(systemId), clazz);
        }

        UnmarshalRecord unmarshalRecord = null;
        boolean isPrimitiveWrapper = false;
        Descriptor xmlDescriptor = null;

        CoreAbstractSession session = null;

        // check for case where the reference class is a primitive wrapper - in this case, we
        // need to use the conversion manager to convert the node's value to the primitive
        // wrapper class, then create, populate and return an XMLRoot. This will be done
        // via XMLRootRecord.
        if (clazz == CoreClassConstants.OBJECT) {

            SAXUnmarshallerHandler saxUnmarshallerHandler = new SAXUnmarshallerHandler(xmlUnmarshaller.getContext());
            try {
                XMLReader xmlReader = getXMLReader(clazz);
                saxUnmarshallerHandler.setXMLReader(xmlReader);
                saxUnmarshallerHandler.setUnmarshaller(xmlUnmarshaller);
                saxUnmarshallerHandler.setKeepAsElementPolicy(KEEP_UNKNOWN_AS_ELEMENT);
                setContentHandler(xmlReader, saxUnmarshallerHandler);
                xmlReader.parse(systemId);
            } catch (IOException e) {
                throw XMLMarshalException.unmarshalException(e);
            } catch (SAXException e) {
                throw convertSAXException(e);
            } finally {
                xmlUnmarshaller.getStringBuffer().reset();
            }
            // resolve any mapping references
            saxUnmarshallerHandler.resolveReferences();
            return saxUnmarshallerHandler.getObject();
        } else {
            // for XMLObjectReferenceMappings we need a non-shared cache, so
            // try and get a Unit Of Work from the XMLContext
            try {
                session = xmlUnmarshaller.getContext().getSession(clazz);
                xmlDescriptor = (Descriptor) session.getDescriptor(clazz);
                unmarshalRecord = xmlUnmarshaller.createUnmarshalRecord(xmlDescriptor, session);
            } catch (XMLMarshalException xme) {
                if (xme.getErrorCode() == XMLMarshalException.DESCRIPTOR_NOT_FOUND_IN_PROJECT) {
                    isPrimitiveWrapper = isPrimitiveWrapper(clazz);
                    if (isPrimitiveWrapper) {
                        unmarshalRecord = xmlUnmarshaller.createRootUnmarshalRecord(clazz);
                    } else {
                        throw xme;
                    }

                } else {
                    throw xme;
                }

            }

        }

        try {
            XMLReader xmlReader = getXMLReader(clazz);
            unmarshalRecord.setXMLReader(xmlReader);
            unmarshalRecord.setUnmarshaller(xmlUnmarshaller);
            setContentHandler(xmlReader, unmarshalRecord);
            xmlReader.setLexicalHandler(unmarshalRecord);
            xmlReader.parse(systemId);
        } catch (IOException e) {
            throw XMLMarshalException.unmarshalException(e);
        } catch (SAXException e) {
            throw convertSAXException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }

        // resolve mapping references
        unmarshalRecord.resolveReferences(session, xmlUnmarshaller.getIDResolver());

        if (isPrimitiveWrapper) {
            return unmarshalRecord.getCurrentObject();
        }
        return xmlDescriptor.wrapObjectInXMLRoot(unmarshalRecord, this.isResultAlwaysXMLRoot);
    }

    @Override
    public Object unmarshal(org.xml.sax.XMLReader xmlReader, InputSource inputSource) {
        try {
            Context xmlContext = xmlUnmarshaller.getContext();
            if (xmlContext.hasDocumentPreservation()) {
                SAXDocumentBuilder saxDocumentBuilder = new SAXDocumentBuilder();
                xmlReader.setContentHandler(saxDocumentBuilder);
                xmlReader.parse(inputSource);
                return unmarshal(saxDocumentBuilder.getDocument().getDocumentElement());
            }
            XMLReader extendedXMLReader;
            if (xmlReader instanceof XMLReader) {
                extendedXMLReader = (XMLReader) xmlReader;
            } else {
                extendedXMLReader = new XMLReader(xmlReader);
            }
            SAXUnmarshallerHandler saxUnmarshallerHandler = new SAXUnmarshallerHandler(xmlContext);
            saxUnmarshallerHandler.setXMLReader(extendedXMLReader);
            saxUnmarshallerHandler.setUnmarshaller(xmlUnmarshaller);
            setContentHandler(extendedXMLReader, saxUnmarshallerHandler);
            extendedXMLReader.parse(inputSource);

            // resolve any mapping references
            saxUnmarshallerHandler.resolveReferences();
            return saxUnmarshallerHandler.getObject();
        } catch (IOException e) {
            throw XMLMarshalException.unmarshalException(e);
        } catch (SAXException e) {
            throw convertSAXException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }
    }

    @Override
    public Object unmarshal(org.xml.sax.XMLReader xmlReader, InputSource inputSource, Class clazz) {
        try {
            Context xmlContext = xmlUnmarshaller.getContext();

            if (xmlContext.hasDocumentPreservation() || (Node.class.isAssignableFrom(clazz) && xmlUnmarshaller.isApplicationXML())) {
                SAXDocumentBuilder saxDocumentBuilder = new SAXDocumentBuilder();
                xmlReader.setContentHandler(saxDocumentBuilder);
                xmlReader.parse(inputSource);
                return unmarshal(saxDocumentBuilder.getDocument().getDocumentElement(), clazz);
            }

            UnmarshalRecord unmarshalRecord = null;
            Descriptor xmlDescriptor = null;

            CoreAbstractSession session = null;
            boolean isPrimitiveWrapper = false;
            // check for case where the reference class is a primitive wrapper - in this case, we
            // need to use the conversion manager to convert the node's value to the primitive
            // wrapper class, then create, populate and return an XMLRoot.  This will be done
            // via XMLRootRecord.
            if (clazz == CoreClassConstants.OBJECT) {
                SAXUnmarshallerHandler saxUnmarshallerHandler = new SAXUnmarshallerHandler(xmlUnmarshaller.getContext());
                saxUnmarshallerHandler.setXMLReader((XMLReader) xmlReader);
                saxUnmarshallerHandler.setUnmarshaller(xmlUnmarshaller);
                saxUnmarshallerHandler.setKeepAsElementPolicy(KEEP_UNKNOWN_AS_ELEMENT);
                xmlReader.setContentHandler(saxUnmarshallerHandler);
                xmlReader.parse(inputSource);

                // resolve any mapping references
                saxUnmarshallerHandler.resolveReferences();
                return saxUnmarshallerHandler.getObject();
            } else {
                // for XMLObjectReferenceMappings we need a non-shared cache, so
                // try and get a Unit Of Work from the XMLContext
                try {
                    session = xmlContext.getSession(clazz);
                    xmlDescriptor = (Descriptor) session.getDescriptor(clazz);
                    unmarshalRecord = xmlUnmarshaller.createUnmarshalRecord(xmlDescriptor, session);
                } catch (XMLMarshalException xme) {
                    if (xme.getErrorCode() == XMLMarshalException.DESCRIPTOR_NOT_FOUND_IN_PROJECT) {
                        isPrimitiveWrapper = isPrimitiveWrapper(clazz);
                        if (isPrimitiveWrapper) {
                            unmarshalRecord = xmlUnmarshaller.createRootUnmarshalRecord(clazz);
                        } else {
                            throw xme;
                        }

                    } else {
                        throw xme;
                    }

                }
            }
            XMLReader extendedXMLReader;
            if (xmlReader instanceof XMLReader) {
                extendedXMLReader = (XMLReader) xmlReader;
            } else {
                extendedXMLReader = new XMLReader(xmlReader);
            }
            unmarshalRecord.setXMLReader(extendedXMLReader);
            unmarshalRecord.setUnmarshaller(xmlUnmarshaller);
            setContentHandler(extendedXMLReader, unmarshalRecord);
            extendedXMLReader.setLexicalHandler(unmarshalRecord);
            extendedXMLReader.parse(inputSource);

            // resolve mapping references
            unmarshalRecord.resolveReferences(session, xmlUnmarshaller.getIDResolver());

            if (isPrimitiveWrapper || clazz == CoreClassConstants.OBJECT) {
                return unmarshalRecord.getCurrentObject();
            }
            return xmlDescriptor.wrapObjectInXMLRoot(unmarshalRecord, this.isResultAlwaysXMLRoot);
        } catch (IOException e) {
            throw XMLMarshalException.unmarshalException(e);
        } catch (SAXException e) {
            throw convertSAXException(e);
        } finally {
            xmlUnmarshaller.getStringBuffer().reset();
        }
    }

    private EclipseLinkException convertSAXException(SAXException saxException) {
        Exception internalException = saxException.getException();
        if (internalException != null) {
            if (EclipseLinkException.class.isAssignableFrom(internalException.getClass())) {
                return (EclipseLinkException) internalException;
            } else {
                return XMLMarshalException.unmarshalException(internalException);
            }
        }
        return XMLMarshalException.unmarshalException(saxException);
    }

    @Override
    public boolean isResultAlwaysXMLRoot() {
        return this.isResultAlwaysXMLRoot;
    }

    @Override
    public void setResultAlwaysXMLRoot(boolean alwaysReturnRoot) {
        this.isResultAlwaysXMLRoot = alwaysReturnRoot;
    }

    private boolean isPrimitiveWrapper(Class clazz) {
        return ((ConversionManager) xmlUnmarshaller.getContext().getSession().getDatasourcePlatform().getConversionManager()).schemaType(clazz) != null
                || CoreClassConstants.XML_GREGORIAN_CALENDAR.isAssignableFrom(clazz)
                || CoreClassConstants.DURATION.isAssignableFrom(clazz);
    }

    /**
     * If a Schema was set on the unmarshaller then wrap the ContentHandler in
     * a ValidatorHandler.
     */
    private void setContentHandler(XMLReader xmlReader, ContentHandler contentHandler) {
        setValidatorHandler(xmlReader);
        xmlReader.setContentHandler(contentHandler);
    }

    private void setValidatorHandler(XMLReader xmlReader) {
        Schema schema = getSchema();
        if (null != schema) {
            ValidatorHandler validatorHandler = schema.newValidatorHandler();
            xmlReader.setValidatorHandler(validatorHandler);
            validatorHandler.setErrorHandler(getErrorHandler());
        }
    }

    @Override
    public void mediaTypeChanged() {
        xmlReader = null;
    }

    private InputStream getInputStreamFromString(String stringValue) {
        if (stringValue.length() == 0) {
            throw org.eclipse.persistence.exceptions.XMLMarshalException.unmarshalFromStringException(stringValue, null);
        }
        URL url = null;
        try {
            url = new URL(stringValue);
            if (url != null) {
                try {
                    return url.openStream();
                } catch (IOException e) {
                    throw org.eclipse.persistence.exceptions.XMLMarshalException.unmarshalFromStringException(stringValue, e);
                }
            }
        } catch (MalformedURLException ex) {
            try {
                return new FileInputStream(stringValue);
            } catch (FileNotFoundException e) {
                throw org.eclipse.persistence.exceptions.XMLMarshalException.unmarshalFromStringException(stringValue, e);
            }
        }
        throw org.eclipse.persistence.exceptions.XMLMarshalException.unmarshalFromStringException(stringValue, null);
    }

    private BufferedReader getBufferedReaderForInputSource(InputSource inputSource) {
        if (inputSource.getByteStream() != null) {
            return new BufferedReader(new InputStreamReader(inputSource.getByteStream()));
        } else if (inputSource.getCharacterStream() != null) {
            return new BufferedReader(inputSource.getCharacterStream());
        } else if (inputSource.getSystemId() != null) {
            InputStream is = getInputStreamFromString(inputSource.getSystemId());
            return new BufferedReader(new InputStreamReader(is));
        }
        throw XMLMarshalException.unmarshalException();
    }

    private MediaType getMediaType(BufferedReader br) {
        int READ_AHEAD_LIMIT = 25;
        try {
            br.mark(READ_AHEAD_LIMIT);
            try {
                char c = 0;
                for (int i = 0; c != -1 && i < READ_AHEAD_LIMIT; i++) {
                    c = (char) br.read();
                    if (c == '[' || c == '{') {
                        return Constants.APPLICATION_JSON;
                    } else if (c == '<') {
                        return Constants.APPLICATION_XML;
                    }

                }

            } finally {
                br.reset();
            }
        } catch (IOException ioException) {
            throw XMLMarshalException.unmarshalException(ioException);
        }
        return xmlUnmarshaller.getMediaType();
    }

    @Override
    public final boolean isSecureProcessingDisabled() {
        return disableSecureProcessing;
    }

    @Override
    public final void setDisableSecureProcessing(boolean disableSecureProcessing) {
        shouldReset = this.disableSecureProcessing ^ disableSecureProcessing;
        this.disableSecureProcessing = disableSecureProcessing;
    }
}
