blob: 0ce0c6d232e3b6411b3647c269afa093aa5eb926 [file] [log] [blame]
/*
* Copyright (c) 1998, 2019 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.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.MarshalRecordContentHandler;
import org.eclipse.persistence.internal.oxm.NamespaceResolver;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.record.XMLFragmentReader;
import org.eclipse.persistence.platform.xml.XMLPlatform;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
/**
* <p>Use this type of MarshalRecord when the marshal target is a Node.</p>
* <p><code>
* XMLContext xmlContext = new XMLContext("session-name");<br>
* XMLMarshaller xmlMarshaller = xmlContext.createMarshaller();<br>
* NodeRecord nodeRecord = new NodeRecord();<br>
* nodeRecord.setDOM(myNode);<br>
* xmlMarshaller.marshal(myObject, nodeRecord);<br>
* </code></p>
* <p>If the marshal(Node) method is called on XMLMarshaller, then the Writer is
* automatically wrapped in a NodeRecord.</p>
* <p><code>
* XMLContext xmlContext = new XMLContext("session-name");<br>
* XMLMarshaller xmlMarshaller = xmlContext.createMarshaller();<br>
* xmlMarshaller.marshal(myObject, myNode);<br>
* </code></p>
* @see org.eclipse.persistence.oxm.XMLMarshaller
*/
public class NodeRecord extends MarshalRecord {
private Document document;
private Node node;
/**
* INTERNAL:
* Default constructor.
*/
public NodeRecord() {
super();
XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
document = xmlPlatform.createDocument();
node = document;
}
/**
* INTERNAL:
* Create a record with the root element name.
*/
public NodeRecord(String rootElementName) {
this(rootElementName, (NamespaceResolver)null);
}
/**
* INTERNAL:
* Create a record with the root element name get the namespace URI from the namespaceResolver.
*/
public NodeRecord(String rootElementName, NamespaceResolver namespaceResolver) {
this();
String rootElementNamespaceURI = resolveNamespace(namespaceResolver, rootElementName);
Node rootElement = document.createElementNS(rootElementNamespaceURI, rootElementName);
document.appendChild(rootElement);
setDOM(rootElement);
}
/**
* INTERNAL:
* Create a record with the local root element name, that is a child of the parent.
*/
public NodeRecord(String localRootElementName, Node parent) {
this(localRootElementName, 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 NodeRecord(String localRootElementName, NamespaceResolver namespaceResolver, Node parent) {
this();
Document document;
if (parent.getNodeType() == Node.DOCUMENT_NODE) {
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 NodeRecord(Node node) {
setDOM(node);
getNamespaceResolver().setDOM(node);
}
@Override
public String getLocalName() {
return node.getLocalName();
}
@Override
public String getNamespaceURI() {
return node.getNamespaceURI();
}
@Override
public void clear() {
}
@Override
public Document getDocument() {
return document;
}
/**
* Return the Node that the object will be marshalled to.
* @return The marshal target.
*/
@Override
public Node getDOM() {
return node;
}
/**
* Set the Node that the object will be marshalled to.
* @param dom The marshal target.
*/
public void setDOM(Node dom) {
int nodeType = dom.getNodeType();
if (Node.DOCUMENT_NODE == nodeType) {
document = (Document)dom;
node = dom;
} else if (Node.ELEMENT_NODE == nodeType || Node.DOCUMENT_FRAGMENT_NODE == nodeType) {
document = dom.getOwnerDocument();
node = dom;
getNamespaceResolver().setDOM(dom);
} else {
throw XMLMarshalException.marshalException(null);
}
}
@Override
public String transformToXML() {
return null;
}
/**
* INTERNAL:
*/
@Override
public void startDocument(String encoding, String version) {}
/**
* INTERNAL:
*/
@Override
public void endDocument() {}
@Override
public void node(Node node, NamespaceResolver namespaceResolver, String uri, String name) {
if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
Attr attr = (Attr) node;
String resolverPfx = null;
if (namespaceResolver != null) {
resolverPfx = namespaceResolver.resolveNamespaceURI(attr.getNamespaceURI());
}
// If the namespace resolver contains a prefix for the attribute's URI,
// use it instead of what is set on the attribute
if (resolverPfx != null) {
attribute(attr.getNamespaceURI(), Constants.EMPTY_STRING, resolverPfx+ Constants.COLON +attr.getLocalName(), attr.getNodeValue());
} else {
attribute(attr.getNamespaceURI(), Constants.EMPTY_STRING, attr.getName(), attr.getNodeValue());
// May need to declare the URI locally
if (attr.getNamespaceURI() != null) {
attribute(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, Constants.EMPTY_STRING, javax.xml.XMLConstants.XMLNS_ATTRIBUTE + Constants.COLON + attr.getPrefix(), attr.getNamespaceURI());
}
}
} else if (node.getNodeType() == Node.TEXT_NODE) {
characters(node.getNodeValue());
} else {
NodeRecordContentHandler mrcHdlr = new NodeRecordContentHandler(this, namespaceResolver);
XMLFragmentReader xfRdr = new XMLFragmentReader(namespaceResolver);
xfRdr.setContentHandler(mrcHdlr);
xfRdr.setLexicalHandler(mrcHdlr);
try {
xfRdr.parse(node, uri, name);
} catch (SAXException sex) {
// Do nothing.
}
}
}
/**
* INTERNAL:
*/
@Override
public void openStartElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
try {
super.openStartElement(xPathFragment, namespaceResolver);
Element element = document.createElementNS(xPathFragment.getNamespaceURI(), getNameForFragment(xPathFragment));
node = node.appendChild(element);
if(xPathFragment.isGeneratedPrefix()){
namespaceDeclaration(xPathFragment.getPrefix(), xPathFragment.getNamespaceURI());
}
} catch (DOMException e) {
throw XMLMarshalException.marshalException(e);
}
}
/**
* INTERNAL:
*/
@Override
public void element(XPathFragment frag) {
Element element = document.createElementNS(frag.getNamespaceURI(), getNameForFragment(frag));
node.appendChild(element);
}
/**
* INTERNAL:
*/
@Override
public void attribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, String value) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
((Element)getDOM()).setAttributeNS(xPathFragment.getNamespaceURI(), getNameForFragment(xPathFragment), value);
}
}
/**
* INTERNAL:
*/
@Override
public void attribute(String namespaceURI, String localName, String qName, String value) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
((Element)getDOM()).setAttributeNS(namespaceURI, qName, value);
}
}
/**
* INTERNAL:
*/
@Override
public void closeStartElement() {
}
/**
* INTERNAL:
*/
@Override
public void endElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
node = node.getParentNode();
}
/**
* INTERNAL:
*/
@Override
public void characters(String value) {
if (value.length() > 0) {
if (node instanceof CDATASection) {
((CDATASection) node).setData(value);
} else {
node.appendChild(document.createTextNode(value));
}
}
}
@Override
public void cdata(String value) {
for (String part : MarshalRecord.splitCData(value)) {
CDATASection cdata = document.createCDATASection(part);
node.appendChild(cdata);
}
}
/**
* INTERNAL:
* Return the namespace uri for the prefix of the given local name
*/
private String resolveNamespace(NamespaceResolver namespaceResolver, String localName) {
int colonIndex = localName.indexOf(':');
if (colonIndex < 0) {
// handle target/default namespace
if (namespaceResolver != null) {
return namespaceResolver.resolveNamespacePrefix(Constants.EMPTY_STRING);
}
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;
}
}
/**
* INTERNAL:
* override so we don't iterate over namespaces when startPrefixMapping doesn't do anything
*/
@Override
public void startPrefixMappings(NamespaceResolver namespaceResolver) {
}
/**
* This class will typically be used in conjunction with an XMLFragmentReader.
* The XMLFragmentReader will walk a given XMLFragment node and report events
* to this class - the event's data is then used to create required attributes
* and elements which are appended to the the enclosing class' document.
*
* @see org.eclipse.persistence.internal.oxm.record.XMLFragmentReader
*/
protected class NodeRecordContentHandler extends MarshalRecordContentHandler implements LexicalHandler {
Map<String, String> prefixMappings;
public NodeRecordContentHandler(NodeRecord nRec, NamespaceResolver resolver) {
super(nRec, resolver);
prefixMappings = new HashMap<>();
}
@Override
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
Element element;
if (namespaceURI == null) {
element = document.createElement(qName);
} else {
element = document.createElementNS(namespaceURI, qName);
}
node = node.appendChild(element);
// Handle attributes
for (int i = 0; i < atts.getLength(); i++) {
marshalRecord.attribute(atts.getURI(i), atts.getLocalName(i), atts.getQName(i), atts.getValue(i));
}
// Handle prefix mappings
if (!prefixMappings.isEmpty()) {
for (Iterator<Map.Entry<String, String>> entries = prefixMappings.entrySet().iterator(); entries.hasNext();) {
Map.Entry<String, String> entry = entries.next();
String namespaceDeclarationPrefix = entry.getKey();
if(null == namespaceDeclarationPrefix || 0 == namespaceDeclarationPrefix.length()) {
String namespaceDeclarationURI = entry.getValue();
if(null == namespaceDeclarationURI) {
element.setAttributeNS(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE , Constants.EMPTY_STRING);
} else {
element.setAttributeNS(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE , namespaceDeclarationURI);
}
} else {
element.setAttributeNS(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE + Constants.COLON + entry.getKey(), entry.getValue());
}
}
prefixMappings.clear();
}
marshalRecord.closeStartElement();
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
String namespaceUri = getNamespaceResolver().resolveNamespacePrefix(prefix);
if(namespaceUri == null || !namespaceUri.equals(uri)) {
prefixMappings.put(prefix, uri);
}
}
@Override
public void startDTD(String name, String publicId, String systemId)
throws SAXException {
}
@Override
public void endDTD() throws SAXException {
}
@Override
public void startEntity(String name) throws SAXException {
EntityReference entityReference = document.createEntityReference(name);
node = node.appendChild(entityReference);
}
@Override
public void endEntity(String name) throws SAXException {
node = node.getParentNode();
}
@Override
public void startCDATA() throws SAXException {
CDATASection cdata = document.createCDATASection(null);
node = node.appendChild(cdata);
}
@Override
public void endCDATA() throws SAXException {
node = node.getParentNode();
}
@Override
public void comment(char[] ch, int start, int length)
throws SAXException {
Comment comment = document.createComment(new String(ch, start, length));
node.appendChild(comment);
}
}
}