blob: e8e4862d9f6b6287190a7c8559a052ed8a64e4eb [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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.internal.oxm.record;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.NamespaceResolver;
import org.eclipse.persistence.internal.oxm.Unmarshaller;
import org.eclipse.persistence.internal.oxm.mappings.Login;
import org.eclipse.persistence.internal.oxm.mappings.Mapping;
import org.eclipse.persistence.internal.oxm.record.namespaces.StackUnmarshalNamespaceResolver;
import org.eclipse.persistence.oxm.documentpreservation.DocumentPreservationPolicy;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ext.Locator2;
/**
* INTERNAL:
* <p><b>Purpose:</b> An implementation of XMLReader for parsing DOM Nodes into SAX events.
* <p><b>Responsibilities:</b><ul>
* <li>Walk the DOM tree and report sax events to the provided content handler</li>
* <li>Report lexical events to the lexical handler if it's provided</li>
* <li>Listen for callbacks from the Mapping-Level framework to handle caching nodes for document preservation</li>
* </ul>
*
*/
public class DOMReader extends XMLReaderAdapter {
private Node currentNode;
private DocumentPreservationPolicy docPresPolicy;
public DOMReader() {
super();
}
public DOMReader(Unmarshaller xmlUnmarshaller) {
super(xmlUnmarshaller);
}
@Override
public void parse(InputSource input) throws SAXException {
if(input instanceof DOMInputSource) {
Node node = ((DOMInputSource) input).getNode();
if(contentHandler != null && contentHandler.getClass() == SAXUnmarshallerHandler.class){
((SAXUnmarshallerHandler)contentHandler).setUnmarshalNamespaceResolver(new StackUnmarshalNamespaceResolver());
}
parse(node);
}
}
public void parse (Node node, String newURI, String newName) throws SAXException {
if(null == contentHandler) {
return;
}
Element rootNode = null;
if(node.getNodeType() == Node.DOCUMENT_NODE) {
rootNode = ((Document)node).getDocumentElement();
} else {
rootNode = (Element)node;
}
if(rootNode == null) {
return;
}
processParentNamespaces(rootNode);
startDocument();
setupLocator(rootNode.getOwnerDocument());
reportElementEvents(rootNode, newURI, newName);
endDocument();
}
public void parse (Node node) throws SAXException {
parse(node, null, null);
}
/**
* Process namespace declarations on parent elements if not the root.
* For each parent node from current to root push each onto a stack,
* then pop each off, calling startPrefixMapping for each XMLNS
* attribute. Using a stack ensures that the parent nodes are
* processed top down.
*
*/
protected void processParentNamespaces(Element element) throws SAXException {
Node parent = element.getParentNode();
// If we're already at the root, do nothing
if (parent != null && parent.getNodeType() == Node.DOCUMENT_NODE) {
return;
}
// Add each parent node up to root to the stack
List<Node> parentElements = new ArrayList<>();
while (parent != null && parent.getNodeType() != Node.DOCUMENT_NODE) {
parentElements.add(parent);
parent = parent.getParentNode();
}
// Pop off each node and call startPrefixMapping for each XMLNS attribute
for (Iterator stackIt = parentElements.iterator(); stackIt.hasNext(); ) {
NamedNodeMap attrs = parentElements.remove(parentElements.size() - 1).getAttributes();
if (attrs != null) {
for (int i=0, length = attrs.getLength(); i < length; i++) {
Attr next = (Attr)attrs.item(i);
String attrPrefix = next.getPrefix();
if (attrPrefix != null && attrPrefix.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) {
contentHandler.startPrefixMapping(next.getLocalName(), next.getValue());
}
}
}
}
}
protected void reportElementEvents(Element elem) throws SAXException {
reportElementEvents(elem, null, null);
}
protected void reportElementEvents(Element elem, String newUri, String newName) throws SAXException {
this.currentNode = elem;
IndexedAttributeList attributes = buildAttributeList(elem);
String namespaceUri = null;
String qname = null;
String lname = null;
if(newName == null){
// Handle null local name
lname = elem.getLocalName();
if (lname == null) {
// If local name is null, use the node name
lname = elem.getNodeName();
qname = lname;
handlePrefixedAttribute(elem);
} else {
qname = getQName(elem);
}
namespaceUri = elem.getNamespaceURI();
if(namespaceUri == null) {
namespaceUri = "";
}
} else {
namespaceUri = newUri;
lname = newName;
qname = newName;
if(namespaceUri != null && isNamespaceAware()){
NamespaceResolver tmpNR = new NamespaceResolver();
tmpNR.setDOM(elem);
String prefix = tmpNR.resolveNamespaceURI(namespaceUri);
if(prefix == null || prefix.length() == 0){
String defaultNamespace = elem.getAttributeNS(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE);
if(defaultNamespace == null){
prefix = tmpNR.generatePrefix();
contentHandler.startPrefixMapping(prefix, namespaceUri);
}else if(defaultNamespace != namespaceUri){
prefix = tmpNR.generatePrefix();
contentHandler.startPrefixMapping(prefix, namespaceUri);
}else{
prefix = Constants.EMPTY_STRING;
}
}
if(prefix != null && prefix.length() >0){
qname = prefix + Constants.COLON + qname;
}
}
}
contentHandler.startElement(namespaceUri, lname, qname, attributes);
handleChildNodes(elem.getChildNodes());
contentHandler.endElement(namespaceUri, lname, qname);
endPrefixMappings(elem);
}
protected IndexedAttributeList buildAttributeList(Element elem) throws SAXException {
IndexedAttributeList attributes = new IndexedAttributeList();
NamedNodeMap attrs = elem.getAttributes();
for (int i = 0, length = attrs.getLength(); i < length; i++) {
Attr next = (Attr)attrs.item(i);
String attrPrefix = next.getPrefix();
if(attrPrefix != null && attrPrefix.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) {
contentHandler.startPrefixMapping(next.getLocalName(), next.getValue());
// Handle XMLNS prefixed attributes
handleNewNamespaceDeclaration(elem, next.getLocalName(), next.getValue());
} else if(attrPrefix == null) {
String name = next.getLocalName();
if(name == null) {
name = next.getNodeName();
}
if(name != null && name.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) {
contentHandler.startPrefixMapping(Constants.EMPTY_STRING, next.getValue());
handleNewNamespaceDeclaration(elem, Constants.EMPTY_STRING, next.getValue());
}
}
if(next.getNamespaceURI() != null && next.getNamespaceURI().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI) && next.getLocalName().equals("type")) {
handleXsiTypeAttribute(next);
}
attributes.addAttribute(next);
}
return attributes;
}
protected void endPrefixMappings(Element elem) throws SAXException {
NamedNodeMap attrs = elem.getAttributes();
for(int i = 0, numOfAtts = attrs.getLength(); i < numOfAtts; i++) {
Attr next = (Attr)attrs.item(i);
String attrPrefix = next.getPrefix();
if (attrPrefix != null && attrPrefix.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) {
contentHandler.endPrefixMapping(next.getLocalName());
} else if(attrPrefix == null) {
String name = next.getLocalName();
if(name == null) {
name = next.getNodeName();
}
if(javax.xml.XMLConstants.XMLNS_ATTRIBUTE.equals(name)) {
contentHandler.endPrefixMapping(Constants.EMPTY_STRING);
}
}
}
}
protected String getQName(Element elem) throws SAXException {
handlePrefixedAttribute(elem);
String prefix = elem.getPrefix();
if (prefix != null && prefix.length() > 0) {
String qname = prefix + Constants.COLON + elem.getLocalName();
return qname;
} else {
return elem.getLocalName();
}
}
protected void handleNewNamespaceDeclaration(Element elem, String emptyString, String value) {
// DO NOTHING
}
protected void handleXsiTypeAttribute(Attr attr) throws SAXException {
}
/**
* Handle prefixed attribute - may need to declare the namespace
* URI locally.
*
*/
protected void handlePrefixedAttribute(Element elem) throws SAXException {
// DO NOTHING
}
protected void handleChildNodes(NodeList children) throws SAXException {
Node nextChild = null;
if(children.getLength() > 0) {
nextChild = children.item(0);
}
while(nextChild != null) {
if(nextChild.getNodeType() == Node.TEXT_NODE) {
char[] value = nextChild.getNodeValue().toCharArray();
contentHandler.characters(value, 0, value.length);
} else if(nextChild.getNodeType() == Node.COMMENT_NODE) {
char[] value = nextChild.getNodeValue().toCharArray();
if (lexicalHandler != null) {
lexicalHandler.comment(value, 0, value.length);
}
} else if(nextChild.getNodeType() == Node.ELEMENT_NODE) {
Element childElement = (Element)nextChild;
reportElementEvents(childElement);
} else if(nextChild.getNodeType() == Node.CDATA_SECTION_NODE) {
if(lexicalHandler != null) {
lexicalHandler.startCDATA();
}
char[] value = ((CDATASection)nextChild).getData().toCharArray();
contentHandler.characters(value, 0, value.length);
if(lexicalHandler != null) {
lexicalHandler.endCDATA();
}
}
nextChild = nextChild.getNextSibling();
}
}
/**
* Trigger an endDocument event on the contenthandler.
*/
protected void endDocument() throws SAXException {
contentHandler.endDocument();
}
/**
* Trigger a startDocument event on the contenthandler.
*/
protected void startDocument() throws SAXException {
contentHandler.startDocument();
}
/**
* An EclipseLink specific callback into the Reader. This allows Objects to be
* associated with the XML Nodes they came from.
*/
@Override
public void newObjectEvent(Object object, Object parent, Mapping selfRecordMapping) {
docPresPolicy.addObjectToCache(object, currentNode, selfRecordMapping);
}
@Override
public Object getCurrentObject(CoreAbstractSession session, Mapping selfRecordMapping) {
//if session == null then this is a marshal of a non-root
//if docPres policy is null, then we never unmarshalled anything, and can
//safely return null;
if(session == null && docPresPolicy == null) {
return null;
}
if(docPresPolicy == null) {
Login login = (Login)session.getDatasourceLogin();
docPresPolicy = login.getDocumentPreservationPolicy();
}
return docPresPolicy.getObjectForNode(currentNode, selfRecordMapping);
}
public DocumentPreservationPolicy getDocPresPolicy() {
return docPresPolicy;
}
public void setDocPresPolicy(DocumentPreservationPolicy policy) {
docPresPolicy = policy;
}
protected void setupLocator(Document doc) {
LocatorImpl locator = new LocatorImpl();
try {
locator.setEncoding(doc.getXmlEncoding());
locator.setXMLVersion(doc.getXmlVersion());
} catch(Exception ex) {
//if unable to invoke these methods, just return and don't invoke
return;
}
this.contentHandler.setDocumentLocator(locator);
}
// Made static final for performance reasons.
/**
* Implementation of Attributes - used to pass along a given node's attributes
* to the startElement method of the reader's content handler.
*/
protected static final class IndexedAttributeList implements org.xml.sax.Attributes {
private List<Attr> attrs;
public IndexedAttributeList() {
attrs = new ArrayList();
}
public void addAttribute(Attr attribute) {
attrs.add(attribute);
}
@Override
public String getQName(int index) {
try {
Attr item = attrs.get(index);
if (item.getName() != null) {
return item.getName();
}
return Constants.EMPTY_STRING;
} catch (IndexOutOfBoundsException iobe) {
return null;
}
}
@Override
public String getType(String namespaceUri, String localName) {
return Constants.CDATA;
}
@Override
public String getType(int index) {
return Constants.CDATA;
}
@Override
public String getType(String qname) {
return Constants.CDATA;
}
@Override
public int getIndex(String qname) {
for (int i=0, size = attrs.size(); i<size; i++) {
if (attrs.get(i).getName().equals(qname)) {
return i;
}
}
return -1;
}
@Override
public int getIndex(String uri, String localName) {
for (int i=0, size = attrs.size(); i<size; i++) {
Attr item = attrs.get(i);
try {
if (item.getNamespaceURI().equals(uri) && item.getLocalName().equals(localName)) {
return i;
}
} catch (Exception x) {}
}
return -1;
}
@Override
public int getLength() {
return attrs.size();
}
@Override
public String getLocalName(int index) {
try {
Attr item = attrs.get(index);
if (item.getLocalName() != null) {
return item.getLocalName();
}
return item.getName();
} catch (IndexOutOfBoundsException iobe) {
return null;
}
}
@Override
public String getURI(int index) {
String uri = attrs.get(index).getNamespaceURI();
if(uri == null) {
uri = Constants.EMPTY_STRING;
}
return uri;
}
@Override
public String getValue(int index) {
return (attrs.get(index)).getValue();
}
@Override
public String getValue(String qname) {
for (int i=0, size = attrs.size(); i<size; i++) {
Attr item = attrs.get(i);
if (item.getName().equals(qname)) {
return item.getValue();
}
}
return null;
}
@Override
public String getValue(String uri, String localName) {
for (int i=0, size = attrs.size(); i<size; i++) {
Attr item = attrs.get(i);
if (item != null) {
String itemNS = item.getNamespaceURI();
// Need to handle null/empty URI
if (item.getNamespaceURI() == null) {
itemNS = Constants.EMPTY_STRING;
}
String itemName = item.getLocalName();
if(itemName == null){
itemName = item.getNodeName();
}
if ((itemNS.equals(uri)) && (itemName != null && itemName.equals(localName))) {
return item.getValue();
}
}
}
return null;
}
}
// Made static final for performance reasons.
protected static final class LocatorImpl implements Locator2 {
private String encoding;
private String version;
public LocatorImpl() {
encoding = "UTF-8";
version = "1.0";
}
@Override
public String getEncoding() {
return encoding;
}
@Override
public int getColumnNumber() {
//not supported here
return 0;
}
@Override
public String getSystemId() {
return Constants.EMPTY_STRING;
}
@Override
public String getPublicId() {
return Constants.EMPTY_STRING;
}
@Override
public String getXMLVersion() {
return version;
}
@Override
public int getLineNumber() {
//not supported
return 0;
}
protected void setEncoding(String enc) {
this.encoding = enc;
}
protected void setXMLVersion(String xmlVersion) {
this.version = xmlVersion;
}
}
}