/*
 * 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:
//     rbarkhouse - 2009-04-14 - 2.0 - Initial implementation

package org.eclipse.persistence.oxm.mappings;

import javax.xml.namespace.QName;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.oxm.ConversionManager;
import org.eclipse.persistence.internal.queries.ContainerPolicy;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.oxm.XMLContext;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.XMLRoot;
import org.eclipse.persistence.oxm.mappings.converters.XMLConverter;
import org.eclipse.persistence.oxm.record.DOMRecord;
import org.eclipse.persistence.oxm.record.XMLRecord;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

/**
 * <p><b>Purpose:</b> An abstract superclass for XMLAnyObjectMapping and XMLAnyCollectionMapping.
 * Maps an attribute of an object to an xs:any construct in the schema.
 *
 *  @see XMLAnyObjectMapping
 *  @see XMLAnyCollectionMapping
 *
 */
public abstract class XMLAbstractAnyMapping extends DatabaseMapping {

    private UnmarshalKeepAsElementPolicy keepAsElementPolicy;
    private boolean isWriteOnly;

    public UnmarshalKeepAsElementPolicy getKeepAsElementPolicy() {
        return keepAsElementPolicy;
    }

    public void setKeepAsElementPolicy(UnmarshalKeepAsElementPolicy keepAsElementPolicy) {
        this.keepAsElementPolicy = keepAsElementPolicy;
    }

    protected XMLDescriptor getDescriptor(XMLRecord xmlRecord, AbstractSession session, QName rootQName) throws XMLMarshalException {
        if (rootQName == null) {
            rootQName = new QName(xmlRecord.getNamespaceURI(), xmlRecord.getLocalName());
        }
        XMLContext xmlContext = xmlRecord.getUnmarshaller().getXMLContext();
        XMLDescriptor xmlDescriptor = xmlContext.getDescriptor(rootQName);
        if (null == xmlDescriptor) {
            if (!(getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT || getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT)) {
                throw XMLMarshalException.noDescriptorWithMatchingRootElement(xmlRecord.getLocalName());
            }
        }
        return xmlDescriptor;
    }

    protected Object buildObjectForNonXMLRoot(ClassDescriptor referenceDescriptor, XMLConverter converter, ObjectBuildingQuery query, DOMRecord record, DOMRecord nestedRecord, JoinedAttributeManager joinManager, AbstractSession session, Node next, Object container, ContainerPolicy containerPolicy) {
        Object objectValue = null;

        if ((referenceDescriptor != null) && (getKeepAsElementPolicy() != UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT)) {
            ObjectBuilder builder = referenceDescriptor.getObjectBuilder();
            objectValue = builder.buildObject(query, nestedRecord, joinManager);
            if (converter != null) {
                objectValue = converter.convertDataValueToObjectValue(objectValue, session, record.getUnmarshaller());
            }
            if (containerPolicy != null) {
                containerPolicy.addInto(objectValue, container, session);
            }
        } else {
            if ((getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT) || (getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT)) {
                XMLPlatformFactory.getInstance().getXMLPlatform().namespaceQualifyFragment((Element) next);
                objectValue = next;
                if (converter != null) {
                    objectValue = converter.convertDataValueToObjectValue(objectValue, session, record.getUnmarshaller());
                }
                if (containerPolicy != null) {
                    containerPolicy.addInto(objectValue, container, session);
                }
            }
        }

        return objectValue;
    }

    /**
     * Uses a given reference descriptor to build an object based on a given DOMRecord.
     * If a converter is provided it is applied to the newly built object.  The
     * reference descriptor will wrap the object in an XMLRoot if required, and the
     * object will be added to the given Container Policy if it is non-null.
     */
    protected Object buildObjectAndWrapInXMLRoot(ClassDescriptor referenceDescriptor, XMLConverter converter, ObjectBuildingQuery query, DOMRecord record, DOMRecord nestedRecord, JoinedAttributeManager joinManager, AbstractSession session, Node next, Object container, ContainerPolicy containerPolicy) {
        ObjectBuilder builder = referenceDescriptor.getObjectBuilder();
        Object objectValue = builder.buildObject(query, nestedRecord, joinManager);
        if (converter != null) {
            objectValue = converter.convertDataValueToObjectValue(objectValue, session, record.getUnmarshaller());
        }
        Object updated = ((XMLDescriptor) referenceDescriptor).wrapObjectInXMLRoot(objectValue, next.getNamespaceURI(), next.getLocalName(), next.getPrefix(), false, record.isNamespaceAware(), record.getUnmarshaller());
        if (containerPolicy != null) {
            containerPolicy.addInto(updated, container, session);
        }
        return updated;
    }

    /**
     * Convenience method that takes a given Node and applies namespace
     * information, converts it if necessary, and adds the resulting
     * object to the given ContainerPolicy if non-null.
     */
    protected Object buildObjectNoReferenceDescriptor(DOMRecord record, XMLConverter converter, AbstractSession session, Node next, Object container, ContainerPolicy cp) {
        XMLPlatformFactory.getInstance().getXMLPlatform().namespaceQualifyFragment((Element) next);
        Object objectValue = next;
        if (converter != null) {
            objectValue = converter.convertDataValueToObjectValue(objectValue, session, record.getUnmarshaller());
        }
        if (cp != null) {
            cp.addInto(objectValue, container, session);
        }
        return objectValue;
    }

    /**
     * Convenience method that takes a given node and checks the first child for
     * TEXT_NODE.  If the first child is a text node containing a non-empty
     * String, the String value will be processed and wrapped in an XMLRoot.
     * If schemaTypeQName represents a default XML type (boolean, dateTime, etc)
     * the String value will be converted to that type.  If a converter is
     * provided it will be applied before wrapping in an XMLRoot.
     */
    protected XMLRoot buildXMLRootForText(Node node, QName schemaTypeQName, XMLConverter converter, AbstractSession session, DOMRecord record) {
        XMLRoot rootValue = null;
        Node textchild = ((Element) node).getFirstChild();
        if ((textchild != null) && (textchild.getNodeType() == Node.TEXT_NODE)) {
            String stringValue = ((Text) textchild).getNodeValue();
            if ((stringValue != null) && stringValue.length() > 0) {
                Object convertedValue = stringValue;
                if (schemaTypeQName != null) {
                    ConversionManager conversionManager = (ConversionManager) session.getDatasourcePlatform().getConversionManager();
                    Class theClass = conversionManager.javaType(schemaTypeQName);
                    if (theClass != null) {
                        convertedValue = conversionManager.convertObject(convertedValue, theClass, schemaTypeQName);
                    }
                }
                if (converter != null) {
                    convertedValue = converter.convertDataValueToObjectValue(convertedValue, session, record.getUnmarshaller());
                }
                rootValue = new XMLRoot();
                rootValue.setLocalName(node.getLocalName());
                rootValue.setSchemaType(schemaTypeQName);
                rootValue.setNamespaceURI(node.getNamespaceURI());
                rootValue.setObject(convertedValue);
            }
        }
        return rootValue;
    }

    /**
     * Convenience method that builds an XMLRoot wrapping a given object.
     * The local name and uri are set using the given Node.
     */
    protected XMLRoot buildXMLRoot(Node node, Object object) {
        XMLRoot xmlRoot = new XMLRoot();
        xmlRoot.setLocalName(node.getLocalName());
        xmlRoot.setNamespaceURI(node.getNamespaceURI());
        xmlRoot.setObject(object);
        return xmlRoot;
    }

    public boolean isWriteOnly() {
        return this.isWriteOnly;
    }

    public void setIsWriteOnly(boolean b) {
        this.isWriteOnly = b;
    }

    public void setAttributeValueInObject(Object object, Object value) throws DescriptorException {
        if(isWriteOnly()) {
            return;
        }
        super.setAttributeValueInObject(object, value);
    }

    public void preInitialize(AbstractSession session) throws DescriptorException {
        getAttributeAccessor().setIsWriteOnly(this.isWriteOnly());
        getAttributeAccessor().setIsReadOnly(this.isReadOnly());
        super.preInitialize(session);
    }
}
