/*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*
*/
package org.wso2.carbon.bpmn.core.types.datatypes.xml.api;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.w3c.dom.UserDataHandler;
import org.wso2.carbon.bpmn.core.types.datatypes.xml.BPMNXmlException;
import org.wso2.carbon.bpmn.core.types.datatypes.xml.Utils;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import java.io.IOException;
/**
* Wrap class wrapping Document object implementing org.w3c.dom.Document as WSO2 BPMN XML datatype adding new functions
*/
public class XMLDocument implements Document {
private Document doc;
private static final Log log = LogFactory.getLog(XMLDocument.class);
public XMLDocument(Document document) {
this.doc = document;
}
/*******************************************************************************************************************
* introduced functions for BPMN START
* ****************************************************************************************************************/
/**
* Function to evaluate xPath query and retrieve relevant element
*
* @param xpathStr xpath expression to evaluate
* @return Returns org.w3c.dom.NodeList if there are more than one elements in the result, Otherwise org.w3c.dom.Node Object is returned
* @throws XPathExpressionException If expression cannot be evaluated
*/
public Object xPath(String xpathStr) throws BPMNXmlException {
if (log.isDebugEnabled()) {
log.debug("Evaluating xPath: " +xpathStr + " on XML :"+ this.toString());
}
return Utils.evaluateXPath(doc, xpathStr);
}
/**
* Function to evaluate xPath query, and return specified return type
*
* @param xpathStr xpath expression to evaluate
* @param returnType The desired return type of xpath evaluation. Supported retrun types : "NODESET", "NODE", "STRING", "NUMBER", "BOOLEAN"
* @return result of xpath evaluation in specified return type
* @throws BPMNXmlException
* @throws XPathExpressionException
*/
public Object xPath(String xpathStr, String returnType) throws BPMNXmlException, XPathExpressionException {
if (returnType.equals(XPathConstants.NODESET.getLocalPart())) {
Utils.evaluateXPath(doc, xpathStr, XPathConstants.NODESET);
} else if (returnType.equals(XPathConstants.NODE.getLocalPart())) {
Utils.evaluateXPath(doc, xpathStr, XPathConstants.NODE);
} else if (returnType.equals(XPathConstants.STRING.getLocalPart())) {
Utils.evaluateXPath(doc, xpathStr, XPathConstants.STRING);
} else if (returnType.equals(XPathConstants.NUMBER.getLocalPart())) {
Utils.evaluateXPath(doc, xpathStr, XPathConstants.NUMBER);
} else if (returnType.equals(XPathConstants.BOOLEAN.getLocalPart())) {
Utils.evaluateXPath(doc, xpathStr, XPathConstants.BOOLEAN);
} else {
//Unknown return type
throw new BPMNXmlException("Unknown return type : " +returnType);
}
return null;
}
/**
* Function to set/replace/update an object (String / Element) to matching the xPath provided. (In case new element
* is added, this api will clone it and merge the new node to the target location pointed by xPath and return the new cloned node)
*
* @param xPathStr xPath to the location object need to set
* @param obj String or Node
* @return returns the node get updated when the set object is String, or returns newly added Node in case object is Element
* @throws XPathExpressionException If expression cannot be evaluated
* @throws BPMNXmlException is thrown due to : Provided XPath and object does not match, provided object is not a Node or String
* result is NodeList, not a Text node or Element
*/
public Node set(String xPathStr, Object obj) throws XPathExpressionException, BPMNXmlException {
NodeList evalResult = (NodeList) Utils.evaluateXPath(this.doc, xPathStr, XPathConstants.NODESET);
if (evalResult.getLength() == 1) {
Node targetNode = evalResult.item(0);
if (obj instanceof String && targetNode instanceof Text) { //if string is provided, assume that user
//need to replace the node value
targetNode.setNodeValue((String) obj);
//return updated Text Node
return targetNode;
} else if ((obj instanceof Integer || obj instanceof Byte ||
obj instanceof Character || obj instanceof Short || obj instanceof Long ||
obj instanceof Float || obj instanceof Double || obj instanceof Boolean) && targetNode instanceof Text) {
//need to replace the node value
targetNode.setNodeValue(obj.toString());
//return updated Text Node
return targetNode;
} else if (obj instanceof Element && targetNode instanceof Element && targetNode.getParentNode() != null) { //if the user provides Node object,
// assume that need to replace the target node
Node targetParent = targetNode.getParentNode();
Node nextSibling = targetNode.getNextSibling();
//remove the target node
targetParent.removeChild(targetNode);
//add new node
Node newNode = doc.importNode((Node) obj, true);
if (nextSibling != null) {
//If next sibling exists we have to put the new node before it
targetParent.insertBefore(newNode, nextSibling);
} else {
targetParent.appendChild(newNode);
}
//return new node
return newNode;
} else { //provided XPath and object to set does not match
throw new BPMNXmlException("Provided XPath and provided object does not match");
}
} else if (evalResult.getLength() > 0) {
throw new BPMNXmlException("Error in provided xPath. Evaluation result is NodeList, not a Text node or Element");
} else {
throw new BPMNXmlException("Error in provided xPath. Evaluation result is not a Text node or Element");
}
}
/**
* Function to append child element to target element
*
* @param xPathToParent xPath to parent node
* @param element element to append
* @return returns the node get appended or returns newly added Node in case object is Element
* @throws XPathExpressionException If expression cannot be evaluated
* @throws BPMNXmlException If no parent node found, the resulting NodeList empty,
* Error in provided xPath. Evaluation result is not a Node or NodeList
*/
public Node appendChild(String xPathToParent, Element element) throws XPathExpressionException, BPMNXmlException {
Object evalResult = Utils.evaluateXPath(doc, xPathToParent);
if (evalResult instanceof Node && evalResult instanceof Element) {
//If xpath evaluated to an Element, will add as child element
Node newNode = doc.importNode((Node) element, true);
return ((Node) evalResult).appendChild(newNode);
} else if (evalResult instanceof NodeList) {
throw new BPMNXmlException((((NodeList)evalResult).getLength() > 0 ?
"xpath does not evaluated to a unique parent node" :
"xPath evaluation failed. Node does not exists for xPath: " + xPathToParent));
} else {
throw new BPMNXmlException("Error in provided xPath. Evaluation result is not a Node." +
"The evaluation result is in type:" + evalResult.getClass().getName());
}
}
/**
* Inserts the node newChild node before the existing node
* @param xPathToTargetNode xPath to target node
* @param element element to insert
* @return returns the node get inserted on success
* @throws XPathExpressionException
* @throws BPMNXmlException
*/
public Node insertBefore(String xPathToTargetNode, Element element) throws XPathExpressionException, BPMNXmlException {
Object evalResult = Utils.evaluateXPath(doc, xPathToTargetNode);
if (evalResult instanceof Node && evalResult instanceof Element) {
Node parentNode = ((Node)evalResult).getParentNode();
if (parentNode != null) {
Node newNode = doc.importNode((Node) element, true);
return parentNode.insertBefore(newNode, (Node)evalResult);
}
throw new BPMNXmlException("Target node is the root node (no parent node found).");
} else if (evalResult instanceof NodeList) {
throw new BPMNXmlException((((NodeList)evalResult).getLength() > 0 ?
"xpath does not evaluated to a unique parent node" :
"xPath evaluation failed. Node does not exists for xPath: " + xPathToTargetNode));
} else {
throw new BPMNXmlException("Error in provided xPath. Evaluation result is not a Node." +
"The evaluation result is in type:" + evalResult.getClass().getName());
}
}
/**
* Function overriding Object.toString(). This will serialize the XML object to string
*
* @return String serializing XML object
*/
@Override
public String toString() {
try {
return StringEscapeUtils.escapeXml(Utils.stringify(this));
} catch (TransformerException e) {
log.error("Error occurred while serializing XMLDocument", e);
//If error occurred while serializing we will return the object string
return ((Object) this).toString();
}
}
/**
* Function to create new XML Element (this is a util method)
*
* @param elementStr xml string that need to convert to representing xml element object
* @return returns created xml element
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public Element createNewElement(String elementStr) throws IOException, SAXException, ParserConfigurationException {
XMLDocument document = Utils.parse(elementStr);
if (document != null) {
return document.getDocumentElement();
}
return null;
}
/*******************************************************************************************************************
* Implemented functions of org.w3c.dom.Document
****************************************************************************************************************/
@Override
public DocumentType getDoctype() {
return doc.getDoctype();
}
@Override
public DOMImplementation getImplementation() {
return doc.getImplementation();
}
@Override
public Element getDocumentElement() {
return doc.getDocumentElement();
}
@Override
public Element createElement(String tagName) throws DOMException {
return doc.createElement(tagName);
}
@Override
public DocumentFragment createDocumentFragment() {
return doc.createDocumentFragment();
}
@Override
public Text createTextNode(String data) {
return doc.createTextNode(data);
}
@Override
public Comment createComment(String data) {
return doc.createComment(data);
}
@Override
public CDATASection createCDATASection(String data) throws DOMException {
return doc.createCDATASection(data);
}
@Override
public ProcessingInstruction createProcessingInstruction(String target, String data) throws DOMException {
return doc.createProcessingInstruction(target, data);
}
@Override
public Attr createAttribute(String name) throws DOMException {
return doc.createAttribute(name);
}
@Override
public EntityReference createEntityReference(String name) throws DOMException {
return doc.createEntityReference(name);
}
@Override
public NodeList getElementsByTagName(String tagname) {
return doc.getElementsByTagName(tagname);
}
@Override
public Node importNode(Node importedNode, boolean deep) throws DOMException {
return doc.importNode(importedNode, deep);
}
@Override
public Element createElementNS(String namespaceURI, String qualifiedName) throws DOMException {
return doc.createElementNS(namespaceURI, qualifiedName);
}
@Override
public Attr createAttributeNS(String namespaceURI, String qualifiedName) throws DOMException {
return doc.createAttributeNS(namespaceURI, qualifiedName);
}
@Override
public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
return doc.getElementsByTagNameNS(namespaceURI, localName);
}
@Override
public Element getElementById(String elementId) {
return doc.getElementById(elementId);
}
@Override
public String getInputEncoding() {
return doc.getInputEncoding();
}
@Override
public String getXmlEncoding() {
return doc.getXmlEncoding();
}
@Override
public boolean getXmlStandalone() {
return doc.getXmlStandalone();
}
@Override
public void setXmlStandalone(boolean xmlStandalone) throws DOMException {
doc.setXmlStandalone(xmlStandalone);
}
@Override
public String getXmlVersion() {
return doc.getXmlVersion();
}
@Override
public void setXmlVersion(String xmlVersion) throws DOMException {
doc.setXmlVersion(xmlVersion);
}
@Override
public boolean getStrictErrorChecking() {
return doc.getStrictErrorChecking();
}
@Override
public void setStrictErrorChecking(boolean strictErrorChecking) {
doc.setStrictErrorChecking(strictErrorChecking);
}
@Override
public String getDocumentURI() {
return doc.getDocumentURI();
}
@Override
public void setDocumentURI(String documentURI) {
doc.getDocumentURI();
}
@Override
public Node adoptNode(Node source) throws DOMException {
return doc.adoptNode(source);
}
@Override
public DOMConfiguration getDomConfig() {
return doc.getDomConfig();
}
@Override
public void normalizeDocument() {
doc.normalizeDocument();
}
@Override
public Node renameNode(Node n, String namespaceURI, String qualifiedName) throws DOMException {
return doc.renameNode(n, namespaceURI, qualifiedName);
}
@Override
public String getNodeName() {
return doc.getNodeName();
}
@Override
public String getNodeValue() throws DOMException {
return doc.getNodeValue();
}
@Override
public void setNodeValue(String nodeValue) throws DOMException {
doc.setNodeValue(nodeValue);
}
@Override
public short getNodeType() {
return doc.getNodeType();
}
@Override
public Node getParentNode() {
return doc.getParentNode();
}
@Override
public NodeList getChildNodes() {
return doc.getChildNodes();
}
@Override
public Node getFirstChild() {
return doc.getFirstChild();
}
@Override
public Node getLastChild() {
return doc.getLastChild();
}
@Override
public Node getPreviousSibling() {
return doc.getPreviousSibling();
}
@Override
public Node getNextSibling() {
return doc.getNextSibling();
}
@Override
public NamedNodeMap getAttributes() {
return doc.getAttributes();
}
@Override
public Document getOwnerDocument() {
return doc.getOwnerDocument();
}
@Override
public Node insertBefore(Node newChild, Node refChild) throws DOMException {
return doc.insertBefore(newChild, refChild);
}
@Override
public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
return doc.replaceChild(newChild, oldChild);
}
@Override
public Node removeChild(Node oldChild) throws DOMException {
return doc.removeChild(oldChild);
}
@Override
public Node appendChild(Node newChild) throws DOMException {
return doc.appendChild(newChild);
}
@Override
public boolean hasChildNodes() {
return doc.hasChildNodes();
}
@Override
public Node cloneNode(boolean deep) {
return doc.cloneNode(deep);
}
@Override
public void normalize() {
doc.normalize();
}
@Override
public boolean isSupported(String feature, String version) {
return doc.isSupported(feature, version);
}
@Override
public String getNamespaceURI() {
return doc.getNamespaceURI();
}
@Override
public String getPrefix() {
return doc.getPrefix();
}
@Override
public void setPrefix(String prefix) throws DOMException {
doc.setPrefix(prefix);
}
@Override
public String getLocalName() {
return doc.getLocalName();
}
@Override
public boolean hasAttributes() {
return doc.hasAttributes();
}
@Override
public String getBaseURI() {
return doc.getBaseURI();
}
@Override
public short compareDocumentPosition(Node other) throws DOMException {
return doc.compareDocumentPosition(other);
}
@Override
public String getTextContent() throws DOMException {
return doc.getTextContent();
}
@Override
public void setTextContent(String textContent) throws DOMException {
doc.setTextContent(textContent);
}
@Override
public boolean isSameNode(Node other) {
return doc.isSameNode(other);
}
@Override
public String lookupPrefix(String namespaceURI) {
return doc.lookupPrefix(namespaceURI);
}
@Override
public boolean isDefaultNamespace(String namespaceURI) {
return doc.isDefaultNamespace(namespaceURI);
}
@Override
public String lookupNamespaceURI(String prefix) {
return doc.lookupNamespaceURI(prefix);
}
@Override
public boolean isEqualNode(Node arg) {
return doc.isEqualNode(arg);
}
@Override
public Object getFeature(String feature, String version) {
return doc.getFeature(feature, version);
}
@Override
public Object setUserData(String key, Object data, UserDataHandler handler) {
return doc.setUserData(key, data, handler);
}
@Override
public Object getUserData(String key) {
return doc.getUserData(key);
}
}