/*
 * 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.oxm.record;

import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.xml.namespace.QName;

import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.oxm.ReferenceResolver;
import org.eclipse.persistence.internal.oxm.UnmarshalXPathEngine;
import org.eclipse.persistence.internal.oxm.XMLConversionManager;
import org.eclipse.persistence.oxm.IDResolver;
import org.eclipse.persistence.oxm.XMLField;
import org.eclipse.persistence.oxm.XMLConstants;
import org.eclipse.persistence.oxm.XMLLogin;
import org.eclipse.persistence.internal.oxm.XPathEngine;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.record.TransformationRecord;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy;
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.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * PUBLIC:
 * Provides a Record/Map API on an XML DOM element.
 */
public class DOMRecord extends XMLRecord implements TransformationRecord {
    private Node dom;
    private Node currentNode;
    private XMLField lastUpdatedField;
    private ReferenceResolver referenceResolver;

    /**
     * INTERNAL:
     * Default constructor.
     */
    public DOMRecord() {
        super();
        setNamespaceResolver(new NamespaceResolver());
        referenceResolver = new ReferenceResolver();
        // Required for subclasses.
    }

    /**
     * INTERNAL:
     * Create a record with the root element name.
     */
    public DOMRecord(String rootElementName) {
        this(rootElementName, (NamespaceResolver)null);
    }

    /**
     * INTERNAL:
     * Create a record with the root element name get the namespace URI from the namespaceResolver.
     */
    public DOMRecord(String rootElementName, NamespaceResolver namespaceResolver) {
        this();
        String rootElementNamespaceURI = resolveNamespace(namespaceResolver, rootElementName);
        setDOM(createNewDocument(rootElementName, rootElementNamespaceURI));
    }

     /**
     * INTERNAL:
     * Create a record with the root element name get the namespace URI from the namespaceResolver.
     */
    public DOMRecord(String rootElementName, String rootElementNamespaceURI) {
        this();
        setDOM(createNewDocument(rootElementName, rootElementNamespaceURI));
    }

    /**
     * INTERNAL:
     * Create a record with the local root element name, that is a child of the parent.
     */
    public DOMRecord(String localRootElementName, Node parent) {
        this(localRootElementName, (NamespaceResolver)null, parent);
    }

    /**
     * INTERNAL:
     * Create a record with the local root element name, that is a child of the parent.
     * Lookup the namespace URI from the namespaceResolver.
     */
    public DOMRecord(String localRootElementName, NamespaceResolver namespaceResolver, Node parent) {
        this();
        Document document;
        if (parent instanceof Document) {
            document = (Document)parent;
        } else {
            document = parent.getOwnerDocument();
        }

        String localRootElementNamespaceURI = resolveNamespace(namespaceResolver, localRootElementName);
        Element child = document.createElementNS(localRootElementNamespaceURI, localRootElementName);
        parent.appendChild(child);
        setDOM(child);
    }

    /**
     * INTERNAL:
     * Create a record with the element.
     */
    public DOMRecord(Element element) {
        this();
        setDOM(element);
    }

    public DOMRecord(Node node) {
        this();
        setDOM(node);
    }

    /**
     * INTERNAL:
     * Create a record with the element.
     */
    public DOMRecord(Document document) {
        this();
        setDOM(document.getDocumentElement());
    }

    /**
     * PUBLIC:
     * Get the local name of the context root element.
     */
    public String getLocalName() {
        String localName = getDOM().getLocalName();
        if (null != localName) {
            return localName;
        }
        return getDOM().getNodeName();
    }

    /**
     * PUBLIC:
     *  Get the namespace URI for the context root element.
     */
    public String getNamespaceURI() {
        return getDOM().getNamespaceURI();
    }

    /**
     * INTERNAL:
     * The ReferenceResolver that is leveraged by key based mappings.
     * @since EclipseLink 2.5.0
     */
    public ReferenceResolver getReferenceResolver() {
        if(null == referenceResolver) {
            referenceResolver = new ReferenceResolver();
        }
        return referenceResolver;
    }

    /**
     * INTERNAL:
     * Set the ReferenceResolver that will be leveraged by key based mappings.
     * @since EclipseLink 2.5.0
     */
    public void setReferenceResolver(ReferenceResolver referenceResolver) {
        this.referenceResolver = referenceResolver;
    }

    /**
     * INTERNAL:
     * Add the field-value pair to the document.
     */
    public void add(DatabaseField key, Object value) {
        // Value may be a direct value, nested record, or collection of values.
        Object nodeValue = convertToNodeValue(value);
        XPathEngine.getInstance().create(convertToXMLField(key), dom, nodeValue, session);
    }

    /**
     * INTERNAL:
     * Convert the value which may be a direct value, nested record, or set of nested records,
     * to a node value usable with the XPathEngine.
     */
    private Object convertToNodeValue(Object value) {
        if (value instanceof List) {
            List values = (List)value;
            Vector nodeValues = new Vector(values.size());
            for (int index = 0; index < values.size(); index++) {
                Object nestedValue = values.get(index);
                nodeValues.add(convertToNodeValue(nestedValue));
            }
            return nodeValues;
        } else if (value instanceof DOMRecord) {
            return ((DOMRecord)value).getDOM();
        } else if (value != null && value.getClass() == XMLEntry.class) {
            XMLEntry entry = (XMLEntry)value;
            entry.setValue(convertToNodeValue(entry.getValue()));
            return entry;
        } else {
            return value;
        }
    }

    /**
     * PUBLIC:
     * Clear the sub-nodes of the DOM.
     */
    public void clear() {
        if(getDOM() instanceof Element) {
            String domName = ((Element)getDOM()).getTagName();
            this.dom = createNewDocument(domName, null);
            this.currentNode = this.dom;
        }
    }

    /**
     * INTERNAL:
     * Clone the row and its values.
     */
    public DOMRecord clone() {
        DOMRecord clone = (DOMRecord)super.clone();
        if (clone != null) {
            clone.setDOM((Element)dom.cloneNode(true));
        }
        return clone;
    }

    /**
     * INTERNAL:
     * Creates a new Document and returns the root element of that document
     */
    public Node createNewDocument(String defaultRootElementName) {
        return createNewDocument(defaultRootElementName, null);
    }

    /**
     * INTERNAL:
     * Creates a new Document and returns the root element of that document
     */
    public Node createNewDocument(String defaultRootElementName, String namespaceURI) {
        XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
        Document document = xmlPlatform.createDocument();

        if (defaultRootElementName == null || defaultRootElementName.length() == 0) {
            DocumentFragment fragment = document.createDocumentFragment();
            return fragment;
        } else {
            Node rootNode = document.createElementNS(namespaceURI, defaultRootElementName);
            document.appendChild(rootNode);
            return document.getDocumentElement();
        }
    }

    /**
     * PUBLIC:
     * Return the document.
     */
    public Document getDocument() {
        return getDOM().getOwnerDocument();
    }

    /**
     * INTERNAL:
     * Check if the field is contained in the row.
     */
    public boolean containsKey(DatabaseField key) {
        XMLField xmlField = convertToXMLField(key);
        NodeList nodeList = UnmarshalXPathEngine.getInstance().selectNodes(dom, xmlField, xmlField.getNamespaceResolver());
        return nodeList.getLength() > 0;
    }

    /**
     * PUBLIC:
     * Check if the value is contained in the row.
     */
    public boolean contains(Object value) {
        return values().contains(value);
    }

    public Object get(DatabaseField key) {
        Object value = getIndicatingNoEntry(key);
        if(value == noEntry) {
            return null;
        }
        return value;
    }
    /**
     * INTERNAL:
     * Given a DatabaseField return the corresponding value from the document
     */
    public Object getIndicatingNoEntry(DatabaseField key) {
        return getIndicatingNoEntry(key, false, false);
    }

    public Object getIndicatingNoEntry(DatabaseField key, boolean shouldReturnNode) {
        return getIndicatingNoEntry(key, shouldReturnNode, false);
    }

    public Object getIndicatingNoEntry(DatabaseField key, boolean shouldReturnNode, boolean checkForXsiNil) {
        XMLField field = convertToXMLField(key);

        // handle 'self' xpath
        if (field.isSelfField()) {
            return this;
        }

        Object result = UnmarshalXPathEngine.getInstance().selectSingleNode(dom, field, field.getNamespaceResolver(), checkForXsiNil);

        if(result == noEntry) {
            if(shouldReturnNode) {
                return null;
            }
            return noEntry;
        }

        if (result == NIL) {
            return NIL;
        }

        Node node = (Node)result;
        if(shouldReturnNode) {
            return node;
        }
        // If a node was not found return null
        if (null == node) {
            return null;
        }

        // For Attributes and Text nodes return their value
        if (Node.ELEMENT_NODE != node.getNodeType()) {
            if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
                getValueFromAttribute((Attr)node, field);
            }
            // For Text, must handle typed elements
            return getValueFromElement((Element)node.getParentNode(), node, field);
        }

        // If an element was found
        return buildNestedRow((Element)node);
    }

    /**
     * INTERNAL:
     * Retrieve the value for the field name.
     */
    public Object getValues(String key) {
        Object value = getValuesIndicatingNoEntry(new XMLField(key));

        if (value == AbstractRecord.noEntry) {
            return null;
        }

        return value;
    }

    /**
     * INTERNAL:
     * Given a DatabaseField, return the corresponding values from the document
     */
    public Object getValues(DatabaseField key) {
        return this.getValues(key, null);
    }

    public Object getValues(DatabaseField key, AbstractNullPolicy nullPolicy) {
        Object value = getValuesIndicatingNoEntry(key, nullPolicy);

        if (value == AbstractRecord.noEntry) {
            return null;
        }

        return value;
    }

    public Object getValuesIndicatingNoEntry(DatabaseField key) {
        return this.getValuesIndicatingNoEntry(key, null);
    }

    public Object getValuesIndicatingNoEntry(DatabaseField key, AbstractNullPolicy nullPolicy) {
        return getValuesIndicatingNoEntry(key, false, nullPolicy);
    }

    public List<XMLEntry> getValuesIndicatingNoEntry(List<DatabaseField> keys) {
        return getValuesIndicatingNoEntry(keys, false);
    }

    public List<XMLEntry> getValuesIndicatingNoEntry(List<DatabaseField> keys, boolean shouldReturnNodes) {
        List<XMLField> xmlFields = convertToXMLField(keys);
        List<XMLEntry> values = UnmarshalXPathEngine.getInstance().selectNodes(dom, xmlFields, xmlFields.get(0).getNamespaceResolver());
        if(shouldReturnNodes) {
            return values;
        }
        for(XMLEntry next:values) {
            Node nextNode = (Node)next.getValue();
            if(!(nextNode.getNodeType() == Node.ELEMENT_NODE)) {
                Object value = getValueFromElement((Element)nextNode.getParentNode(), nextNode, next.getXMLField());
                next.setValue(value);
            } else {
                next.setValue(buildNestedRow((Element)nextNode));
            }
        }
        return values;
    }

    /**
     * INTERNAL:
     * Given a DatabaseField, return the corresponding values from the document
     */
    public Object getValuesIndicatingNoEntry(DatabaseField key, boolean shouldReturnNodes) {
        return this.getValuesIndicatingNoEntry(key, shouldReturnNodes, null);
    }

    public Object getValuesIndicatingNoEntry(DatabaseField key, boolean shouldReturnNodes, AbstractNullPolicy nullPolicy) {
        XMLField field = convertToXMLField(key);
        NodeList nodeList = UnmarshalXPathEngine.getInstance().selectNodes(dom, field, field.getNamespaceResolver(), nullPolicy);

        // If a node was not found return null
        if (null == nodeList) {
            return null;
        }
        int resultSize = nodeList.getLength();
        Vector result = new Vector(resultSize);
        if (resultSize == 0) {
            return result;
        }
        if(shouldReturnNodes) {
            //just copy all the nodes into the result vector and return it
            for(int i = 0; i < resultSize; i++) {
                result.add(nodeList.item(i));
            }
            return result;
        }
        // Assumption:  NodeList contains nodes of the same type
        Node firstNode = nodeList.item(0);
        if ((firstNode == null) || (firstNode.getNodeType() != Node.ELEMENT_NODE)) {
            if (field.usesSingleNode() && (resultSize == 1)) {
                Node next = nodeList.item(0);
                if (next == null) {
                    result.add(null);
                } else {
                    Vector list = new Vector();
                    String sourceObject = next.getNodeValue();

                    StringTokenizer tokenizer = new StringTokenizer(sourceObject, " ");
                    while (tokenizer.hasMoreElements()) {
                        String token = tokenizer.nextToken();
                        Object nextItem = convertValue((Element)next.getParentNode(), field, token);
                        list.add(nextItem);
                    }
                    return list;
                }
            }
            for (int x = 0; x < resultSize; x++) {
                Node next = nodeList.item(x);
                if (next == null) {
                    result.add(null);
                } else {
                    result.add(getValueFromElement((Element)next.getParentNode(), next, field));
                }
            }
        } else {
            for (int x = 0; x < resultSize; x++) {
                result.add(buildNestedRow((Element)nodeList.item(x)));
            }
        }

        return result;
    }

    private Object getValueFromAttribute(Attr node, XMLField key) {
        currentNode = node.getOwnerElement();
        Object convertedValue = key.convertValueBasedOnSchemaType(node.getNodeValue(), (XMLConversionManager) session.getDatasourcePlatform().getConversionManager(), this);
        currentNode = getDOM();
        return convertedValue;

    }

    private Object getValueFromElement(Element node, Node textChild, Field key) {
        Object value = textChild.getNodeValue();
        return convertValue(node, key, value);
    }

    private Object convertValue(Element node, Field key, Object value) {
        XMLConversionManager xmlCnvMgr = (XMLConversionManager) session.getDatasourcePlatform().getConversionManager();
        if (key.isTypedTextField() && (node != null)) {
            String schemaType = node.getAttributeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XMLConstants.SCHEMA_TYPE_ATTRIBUTE);
            if ((null != schemaType) && (schemaType.length() > 0)) {
                QName qname = null;
                int index = schemaType.indexOf(XMLConstants.COLON);
                if (index == -1) {
                    qname = new QName(schemaType);
                    Class convertClass = key.getJavaClass(qname, xmlCnvMgr);
                    return xmlCnvMgr.convertObject(value, convertClass);
                } else {
                    String prefix = schemaType.substring(0, index);
                    String localPart = schemaType.substring(index + 1);
                    XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
                    String url = xmlPlatform.resolveNamespacePrefix(node, prefix);
                    qname = new QName(url, localPart);
                    Class convertClass = key.getJavaClass(qname, xmlCnvMgr);
                    return xmlCnvMgr.convertObject(value, convertClass, qname);
                }
            }
        }
        currentNode = node;
        Object convertedValue = key.convertValueBasedOnSchemaType(value, xmlCnvMgr, this);
        currentNode = getDOM();
        return convertedValue;
    }

    /**
     * INTERNAL:
     * Build the nested record, this can be overwriten by subclasses to use their subclass instance.
     */
    public XMLRecord buildNestedRow(Element element) {
        DOMRecord record = new DOMRecord(element);
        record.setUnmarshaller(this.getUnmarshaller());
        record.setOwningObject(this.getCurrentObject());
        record.setDocPresPolicy(this.getDocPresPolicy());
        record.setReferenceResolver(referenceResolver);
        return record;
    }

    /**
    * PUBLIC:
    * Return the DOM.
    */
    public Node getDOM() {
        return dom;
    }

    /**
    * INTERNAL:
    * Set the field value into the DOM.
    * The field name must be a valid simple XPath expression.
    */
    public Object put(DatabaseField key, Object value) {
        // Value may be a direct value, nested record, or collection of values.
        XMLField field = convertToXMLField(key);
        Object nodeValue = convertToNodeValue(value);
        NodeList replaced = null;
        boolean isEmptyCollection = false;
        if (nodeValue instanceof Collection) {
            isEmptyCollection = ((Collection)nodeValue).size() == 0;
            replaced = XPathEngine.getInstance().replaceCollection(convertToXMLField(key), dom, (Collection)nodeValue, session);
        } else {
            replaced = XPathEngine.getInstance().replaceValue(convertToXMLField(key), dom, nodeValue, session);
        }
        if (replaced.getLength() == 0) {
            // Replace does nothing if the node did not exist, return no nodes.
            XPathEngine.getInstance().create(convertToXMLField(key), dom, nodeValue, lastUpdatedField, getDocPresPolicy(), session);
        } else if (replaced.item(0) == getDOM()) {
            // If the root element/record element was changed must update the record's reference.
            setDOM(getDocument().getDocumentElement());
        }
        if(!field.getXPathFragment().isAttribute() && !field.getXPathFragment().nameIsText()) {
            if(value != null && !isEmptyCollection) {
                this.lastUpdatedField = field;
            }
        }
        return replaced;
    }

    public Object put(List<XMLField> xmlFields, List<XMLEntry> values) {
        Vector valuesToWrite = (Vector)convertToNodeValue(values);
        List<XMLEntry> replaced = null;

        replaced = XPathEngine.getInstance().replaceCollection(xmlFields, valuesToWrite, dom, getDocPresPolicy(), lastUpdatedField, session);
        if(replaced.size() == 0) {
            XPathEngine.getInstance().create(xmlFields, dom, valuesToWrite, lastUpdatedField, getDocPresPolicy(), session);
        }
        return replaced;
    }

    public Object put(Object key, Object value) throws ValidationException {
        if (key instanceof String) {
            return put((String)key, value);
        } else if (key instanceof DatabaseField) {
            return put((DatabaseField)key, value);
        } else if (key instanceof List) {
            return put((List<XMLField>)key, (List<XMLEntry>)value);
        } else {
            throw ValidationException.onlyFieldsAreValidKeysForDatabaseRows();
        }
    }

    /**
     * INTERNAL:
     * Remove the field key from the row.
     */
    public Object remove(DatabaseField key) {
        return XPathEngine.getInstance().remove(convertToXMLField(key), dom, true);
    }

    /**
     * INTERNAL:
     * replaces the value at index with value
     */
    public void replaceAt(Object value, int index) {
        throw XMLMarshalException.operationNotSupported("replaceAt(Object value, int index)");
    }

    /**
     * PUBLIC:
     */
    public Set entrySet() {
        int size = this.size();
        Map tempMap = new HashMap(size);
        Vector fields = getFields();
        Vector values = getValues();
        for (int i = 0; i < size; i++) {
            tempMap.put(fields.elementAt(i), values.elementAt(i));
        }
        return tempMap.entrySet();
    }

    /**
     * INTERNAL:
     * Setting fields vector will not update the document so this is not supported
     */
    protected void setFields(Vector fields) throws XMLMarshalException {
        throw XMLMarshalException.operationNotSupported("setField(Vector fields)");
    }

    /**
     * INTERNAL:
     * This should not be used, but added some support for it as
     * is called from some places such as sdk call used in the descriptor to define operation not supported,
     * may also be called from toplin in some places.
     */
    public Vector getFields() {
        int length = getDOM().getChildNodes().getLength();
        Node nextNode = null;
        if(length > 0) {
            nextNode = getDOM().getChildNodes().item(0);
        }
        Vector fields = new Vector(length);
        while(nextNode != null) {
            fields.add(new DatabaseField(nextNode.getNodeName()));
            nextNode = nextNode.getNextSibling();
        }
        return fields;
    }

    /**
     * INTERNAL:
     * This should not be used, but added some support for it as
     * is called from some places such as sdk call used in the descriptor to define operation not supported,
     * may also be called from TopLink in some places.
     */
    public Vector getValues() {
        int length = getDOM().getChildNodes().getLength();
        Node nextNode = null;
        if(length > 0) {
            nextNode = getDOM().getFirstChild();
        }
        Vector values = new Vector(length);
        while(nextNode != null) {
            values.add(nextNode);
            nextNode = nextNode.getNextSibling();
        }
        return values;
    }

    /**
     * INTERNAL:
     * Setting values vector will not update the document so this is not supported
     */
    protected void setValues(Vector values) throws XMLMarshalException {
        throw XMLMarshalException.operationNotSupported("setValues(Vector values)");
    }

    /**
     * INTERNAL:
     * Sets the dom and updated document to be the owner document of the given element
     */
    public void setDOM(Node element) {
        this.dom = element;
        this.currentNode = element;
        this.getNamespaceResolver().setDOM(element);
    }

    public void setDOM(Element element) {
        this.dom = element;
        this.currentNode = element;
        this.getNamespaceResolver().setDOM(element);
    }

    /**
     * INTERNAL:
     * Print the dom XML string.
     */
    public String toString() {
        StringWriter writer = new StringWriter();
        writer.write(Helper.getShortClassName(getClass()));
        writer.write("(");
        transformToWriter(writer);
        writer.write(")");
        return writer.toString();
    }

    /**
     * PUBLIC:
     * Return the set of element names from the DOM.
     */
    public Set keySet() {
        int length = getDOM().getChildNodes().getLength();
        HashSet keys = new HashSet(length);
        for (int index = 0; index < length; index++) {
            keys.add(getDOM().getChildNodes().item(index).getNodeName());
        }
        return keys;
    }

    /**
     * PUBLIC:
     * Return the collection of element values from the DOM.
     */
    public Collection values() {
        int length = getDOM().getChildNodes().getLength();
        Vector values = new Vector(length);
        for (int index = 0; index < length; index++) {
            values.add(getDOM().getChildNodes().item(index));
        }
        return values;
    }

    /**
     * Return the number of elements in the DOM.
     */
    public int size() {
        return getDOM().getAttributes().getLength() + getDOM().getChildNodes().getLength();
    }

    /**
     * Set the XML from an XML string.
     */
    public void transformFromXML(String xml) {
        Reader reader = new StringReader(xml);
        transformFromXML(reader);
    }

    /**
     * INTERNAL:
     * Return the namespace uri for the prefix of the given local name
     */
    private String resolveNamespace(NamespaceResolver namespaceResolver, String localName) {
        if(localName == null) {
            return null;
        }
        int colonIndex = localName.indexOf(XMLConstants.COLON);
        if (colonIndex < 0) {
            // handle target/default namespace
            if (namespaceResolver != null) {
                return namespaceResolver.getDefaultNamespaceURI();
            }
            return null;
        } else {
            if (namespaceResolver == null) {
                //throw an exception if the name has a : in it but the namespaceresolver is null
                throw XMLMarshalException.namespaceResolverNotSpecified(localName);
            }
            String prefix = localName.substring(0, colonIndex);
            String uri = namespaceResolver.resolveNamespacePrefix(prefix);
            if (uri == null) {
                //throw an exception if the prefix is not found in the namespaceresolver
                throw XMLMarshalException.namespaceNotFound(prefix);
            }
            return uri;
        }
    }

    public void setSession(AbstractSession session) {
        this.session = session;
        if (session != null && session.getDatasourceLogin() instanceof XMLLogin) {
            this.equalNamespaceResolvers = ((XMLLogin) session.getDatasourceLogin()).hasEqualNamespaceResolvers();
        }
    }

    /**
     * Set the XML from an XML reader.
     */
    public void transformFromXML(Reader reader) {
        XMLParser parser = XMLPlatformFactory.getInstance().getXMLPlatform().newXMLParser();
        Document document = parser.parse(reader);
        setDOM(document.getDocumentElement());
    }

    /**
     * Return the XML string representation of the DOM.
     */
    public String transformToXML() {
        StringWriter writer = new StringWriter();
        transformToWriter(writer);
        return writer.toString();
    }

    /**
     * Write the XML string representation of the DOM.
     */
    public void transformToWriter(Writer writer) {
        XMLTransformer xmlTransformer = XMLPlatformFactory.getInstance().getXMLPlatform().newXMLTransformer();
        xmlTransformer.transform(this.getDOM(), writer);
    }

    public String resolveNamespacePrefix(String prefix) {
        XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
        return xmlPlatform.resolveNamespacePrefix(currentNode, prefix);
    }

    /**
     * INTERNAL:
     * If the UnmarshalRecord has a ReferenceResolver, tell it to resolve its
     * references.
     * @since EclipseLink 2.5.0
     */
    public void resolveReferences(CoreAbstractSession abstractSession, IDResolver idResolver) {
        if(null != referenceResolver) {
            referenceResolver.resolveReferences(abstractSession, idResolver, unmarshaller.getErrorHandler());
        }
    }

}
