/*
 * Copyright (c) 2011, 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:
//     Blaise Doughan - 2.2 - initial implementation
package org.eclipse.persistence.oxm.record;

import java.util.HashMap;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.validation.Schema;
import javax.xml.validation.ValidatorHandler;

import org.eclipse.persistence.internal.core.helper.CoreField;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.Marshaller;
import org.eclipse.persistence.internal.oxm.Namespace;
import org.eclipse.persistence.internal.oxm.NamespaceResolver;
import org.eclipse.persistence.internal.oxm.XMLMarshaller;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.XPathNode;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.oxm.schema.XMLSchemaReference;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class ValidatingMarshalRecord extends MarshalRecord<Marshaller> {

    private MarshalRecord marshalRecord;
    private ContentHandlerRecord validatingRecord;

    public ValidatingMarshalRecord(MarshalRecord marshalRecord, XMLMarshaller xmlMarshaller) {
        this.marshalRecord = marshalRecord;
        Schema schema = xmlMarshaller.getSchema();
        ValidatorHandler validatorHandler = schema.newValidatorHandler();
        validatorHandler.setErrorHandler(new ValidatingMarshalRecordErrorHandler(marshalRecord, xmlMarshaller.getErrorHandler()));
        if(xmlMarshaller.isFragment()) {
            try {
                validatorHandler.startDocument();
            } catch (SAXException e) {
            }
        }
        validatingRecord = new ContentHandlerRecord();
        validatingRecord.setMarshaller(xmlMarshaller);
        validatingRecord.setContentHandler(validatorHandler);
        validatingRecord.setEqualNamespaceResolvers(marshalRecord.hasEqualNamespaceResolvers());
    }

    @Override
    public void startDocument(String encoding, String version) {
        validatingRecord.startDocument(encoding, version);
        marshalRecord.startDocument(encoding, version);
    }

    @Override
    public void endDocument() {
        validatingRecord.endDocument();
        marshalRecord.endDocument();
    }

    @Override
    public void element(XPathFragment frag) {
        validatingRecord.element(frag);
        marshalRecord.element(frag);
    }

    @Override
    public void attribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, String value) {
        validatingRecord.attribute(xPathFragment, namespaceResolver, value);
        marshalRecord.attribute(xPathFragment, namespaceResolver, value);
    }

    @Override
    public void attribute(String namespaceURI, String localName, String qName, String value) {
        validatingRecord.attribute(namespaceURI, localName, qName, value);
        marshalRecord.attribute(namespaceURI, localName, qName, value);
    }

    @Override
    public void closeStartElement() {
        validatingRecord.closeStartElement();
        marshalRecord.closeStartElement();
    }

    @Override
    public void endElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
        validatingRecord.endElement(xPathFragment, namespaceResolver);
        marshalRecord.endElement(xPathFragment, namespaceResolver);
    }

    @Override
    public HashMap getPositionalNodes() {
        return marshalRecord.getPositionalNodes();
    }

    @Override
    public void addGroupingElement(XPathNode xPathNode) {
        validatingRecord.addGroupingElement(xPathNode);
        marshalRecord.addGroupingElement(xPathNode);
    }

    @Override
    public void removeGroupingElement(XPathNode xPathNode) {
        validatingRecord.removeGroupingElement(xPathNode);
        marshalRecord.removeGroupingElement(xPathNode);
    }

    @Override
    public void add(CoreField key, Object value) {
        validatingRecord.add(key, value);
        marshalRecord.add(key, value);
    }

    @Override
    public Object put(CoreField key, Object value) {
        validatingRecord.put(key, value);
        return marshalRecord.put(key, value);
    }

    @Override
    public void namespaceDeclarations(NamespaceResolver namespaceResolver) {
        validatingRecord.namespaceDeclarations(namespaceResolver);
        marshalRecord.namespaceDeclarations(namespaceResolver);
    }

    @Override
    public void startPrefixMapping(String prefix, String namespaceURI) {
        validatingRecord.startPrefixMapping(prefix, namespaceURI);
        marshalRecord.startPrefixMapping(prefix, namespaceURI);
    }

    @Override
    public void startPrefixMappings(NamespaceResolver namespaceResolver) {
        validatingRecord.startPrefixMappings(namespaceResolver);
        marshalRecord.startPrefixMappings(namespaceResolver);
    }

    @Override
    public void endPrefixMapping(String prefix) {
        validatingRecord.endPrefixMapping(prefix);
        marshalRecord.endPrefixMapping(prefix);
    }

    @Override
    public void endPrefixMappings(NamespaceResolver namespaceResolver) {
        validatingRecord.endPrefixMappings(namespaceResolver);
        marshalRecord.endPrefixMappings(namespaceResolver);
    }

    @Override
    public void openStartElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
        validatingRecord.openStartElement(xPathFragment, namespaceResolver);
        marshalRecord.openStartElement(xPathFragment, namespaceResolver);
    }

    @Override
    public XPathFragment openStartGroupingElements(NamespaceResolver namespaceResolver) {
        validatingRecord.openStartGroupingElements(namespaceResolver);
        return marshalRecord.openStartGroupingElements(namespaceResolver);
    }

    @Override
    public void closeStartGroupingElements(XPathFragment groupingFragment) {
        validatingRecord.closeStartGroupingElements(groupingFragment);
        marshalRecord.closeStartGroupingElements(groupingFragment);
    }

    @Override
    protected void addPositionalNodes(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
        validatingRecord.addPositionalNodes(xPathFragment, namespaceResolver);
        marshalRecord.addPositionalNodes(xPathFragment, namespaceResolver);
    }

    @Override
    public void characters(String value) {
        validatingRecord.characters(value);
        marshalRecord.characters(value);
    }

    @Override
    public void cdata(String value) {
        validatingRecord.cdata(value);
        marshalRecord.cdata(value);
    }

    @Override
    public void node(Node node, NamespaceResolver resolver, String uri, String name) {
        validatingRecord.node(node, resolver, uri, name);
        marshalRecord.node(node, resolver, uri, name);
    }

    @Override
    public String getLocalName() {
        return marshalRecord.getLocalName();
    }

    @Override
    public String getNamespaceURI() {
        return marshalRecord.getNamespaceURI();
    }

    @Override
    public void clear() {
        validatingRecord.clear();
        marshalRecord.clear();
    }

    @Override
    public Document getDocument() {
        return marshalRecord.getDocument();
    }

    @Override
    public Node getDOM() {
        return marshalRecord.getDOM();
    }

    @Override
    public String transformToXML() {
        return marshalRecord.transformToXML();
    }

    @Override
    public String resolveNamespacePrefix(String prefix) {
        return marshalRecord.resolveNamespacePrefix(prefix);
    }

    @Override
    public Marshaller getMarshaller() {
        return marshalRecord.getMarshaller();
    }

    @Override
    public void setMarshaller(Marshaller marshaller) {
        super.setMarshaller(marshaller);

        validatingRecord.setMarshaller(marshaller);
        marshalRecord.setMarshaller(marshaller);
    }

    @Override
    public Object getOwningObject() {
        return marshalRecord.getOwningObject();
    }

    @Override
    public void setOwningObject(Object obj) {
        validatingRecord.setOwningObject(obj);
        marshalRecord.setOwningObject(obj);
    }

    @Override
    public void setLeafElementType(QName type) {
        validatingRecord.setLeafElementType(type);
        marshalRecord.setLeafElementType(type);
    }

    @Override
    public void setNamespaceResolver(NamespaceResolver nr) {
        validatingRecord.setNamespaceResolver(nr);
        marshalRecord.setNamespaceResolver(nr);
    }

    @Override
    public NamespaceResolver getNamespaceResolver() {
        return marshalRecord.getNamespaceResolver();
    }

    @Override
    public CoreAbstractSession getSession() {
        return marshalRecord.getSession();
    }

    @Override
    public void setSession(CoreAbstractSession session) {
        super.setSession(session);
        validatingRecord.setSession(session);
        marshalRecord.setSession(session);
    }

    @Override
    public boolean isXOPPackage() {
        return marshalRecord.isXOPPackage();
    }

    @Override
    public void beforeContainmentMarshal(Object child) {
        marshalRecord.beforeContainmentMarshal(child);
    }

    @Override
    public void afterContainmentMarshal(Object parent, Object child) {
        marshalRecord.afterContainmentMarshal(parent, child);
    }

    private static class ValidatingMarshalRecordErrorHandler implements ErrorHandler {

        private MarshalRecord marshalRecord;
        private ErrorHandler errorHandler;

        public ValidatingMarshalRecordErrorHandler(MarshalRecord marshalRecord, ErrorHandler errorHandler) {
            this.marshalRecord = marshalRecord;
            this.errorHandler = errorHandler;
        }

        public void warning(SAXParseException exception) throws SAXException {
            if(null == errorHandler) {
                throw exception;
            }
            errorHandler.warning(marshalSAXParseException(exception));
        }

        public void error(SAXParseException exception) throws SAXException {
            if(null == errorHandler) {
                throw exception;
            }
            errorHandler.error(marshalSAXParseException(exception));
        }

        public void fatalError(SAXParseException exception) throws SAXException {
            if(null == errorHandler) {
                throw exception;
            }
            errorHandler.fatalError(marshalSAXParseException(exception));
        }

        private MarshalSAXParseException marshalSAXParseException(SAXParseException exception) {
            return new MarshalSAXParseException(exception.getLocalizedMessage(), exception.getPublicId(), exception.getSystemId(), exception.getLineNumber(), exception.getColumnNumber(), exception.getException(), marshalRecord.getOwningObject());
        }

    }

    public static class MarshalSAXParseException extends SAXParseException {

        private Object object;

        public MarshalSAXParseException(String message, String publicId,
                String systemId, int lineNumber, int columnNumber, Exception e, Object object) {
            super(message, publicId, systemId, lineNumber, columnNumber, e);
            this.object = object;
        }

        public Object getObject() {
            return object;
        }

    }

    @Override
    public void writeHeader() {
        marshalRecord.writeHeader();
    }

    /**
     * @since EclipseLink 2.5.0
     */
    @Override
    public List<Namespace> addExtraNamespacesToNamespaceResolver(
            Descriptor descriptor, CoreAbstractSession session,
            boolean allowOverride, boolean ignoreEqualResolvers) {
        validatingRecord.addExtraNamespacesToNamespaceResolver(descriptor, session, allowOverride, ignoreEqualResolvers);
        return marshalRecord.addExtraNamespacesToNamespaceResolver(descriptor, session, allowOverride, ignoreEqualResolvers);
    }

    /**
     * @since EclipseLink 2.5.0
     */
    @Override
    public void removeExtraNamespacesFromNamespaceResolver(
            List<Namespace> extraNamespaces, CoreAbstractSession session) {
        validatingRecord.removeExtraNamespacesFromNamespaceResolver(extraNamespaces, session);
        marshalRecord.removeExtraNamespacesFromNamespaceResolver(extraNamespaces, session);
    }

    /**
     * @since EclipseLink 2.5.0
     */
    @Override
    public void attributeWithoutQName(String namespaceURI, String localName,
            String prefix, String value) {
        validatingRecord.attributeWithoutQName(namespaceURI, localName, prefix, value);
        marshalRecord.attributeWithoutQName(namespaceURI, localName, prefix, value);
    }

    /**
     * @since EclipseLink 2.5.0
     */
    @Override
    public boolean addXsiTypeAndClassIndicatorIfRequired(
            Descriptor xmlDescriptor, Descriptor referenceDescriptor,
            Field xmlField, boolean isRootElement) {
        validatingRecord.addXsiTypeAndClassIndicatorIfRequired(xmlDescriptor, referenceDescriptor, xmlField, isRootElement);
        return marshalRecord.addXsiTypeAndClassIndicatorIfRequired(xmlDescriptor, referenceDescriptor, xmlField, isRootElement);
    }

    /**
     * @since EclipseLink 2.5.0
     */
    @Override
    public boolean addXsiTypeAndClassIndicatorIfRequired(
            Descriptor xmlDescriptor, Descriptor referenceDescriptor,
            Field xmlField, Object originalObject, Object obj,
            boolean wasXMLRoot, boolean isRootElement) {
        validatingRecord.setNamespaceResolver(new NamespaceResolver(marshalRecord.getNamespaceResolver()));
        validatingRecord.addXsiTypeAndClassIndicatorIfRequired(xmlDescriptor, referenceDescriptor, xmlField, originalObject, obj, wasXMLRoot, isRootElement);
        return marshalRecord.addXsiTypeAndClassIndicatorIfRequired(xmlDescriptor, referenceDescriptor, xmlField, originalObject, obj, wasXMLRoot, isRootElement);
    }

    /**
     * @since EclipseLink 2.5.0
     */
    @Override
    public void writeXsiTypeAttribute(Descriptor descriptor, String typeUri,
            String typeLocal, String typePrefix, boolean addToNamespaceResolver) {
        validatingRecord.writeXsiTypeAttribute(descriptor, typeUri, typeLocal, typePrefix, addToNamespaceResolver);
        marshalRecord.writeXsiTypeAttribute(descriptor, typeUri, typeLocal, typePrefix, addToNamespaceResolver);
    }

    /**
     * @since EclipseLink 2.5.0
     */
    @Override
    public void writeXsiTypeAttribute(Descriptor xmlDescriptor,
            XMLSchemaReference xmlRef, boolean addToNamespaceResolver) {
        validatingRecord.writeXsiTypeAttribute(xmlDescriptor, xmlRef, addToNamespaceResolver);
        marshalRecord.writeXsiTypeAttribute(xmlDescriptor, xmlRef, addToNamespaceResolver);
    }

    @Override
    public void setXOPPackage(boolean isXOPPackage) {
        validatingRecord.setXOPPackage(isXOPPackage);
        marshalRecord.setXOPPackage(isXOPPackage);
    }

}
