/** Copyright (C) 2012 Delcyon, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.delcyon.capo.xml; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.util.logging.Level; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactoryConfigurationException; import javax.xml.xpath.XPathFunctionResolver; import org.w3c.dom.Attr; 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 com.delcyon.capo.CapoApplication; import com.delcyon.capo.datastream.NullOutputStream; import com.delcyon.capo.datastream.stream_attribute_filter.MD5FilterOutputStream; import com.delcyon.capo.server.CapoServer; import com.delcyon.capo.util.NamespaceContextMap; import com.delcyon.capo.xml.cdom.CDocument; /** * @author jeremiah * */ public class XPath { private static XPathFactory xPathFactory = null;; static { try { //try local class loader first, then downgrade to system, then do it the old way... xPathFactory = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI, "net.sf.saxon.xpath.XPathFactoryImpl", XPath.class.getClassLoader()); } catch (Throwable e) { System.err.println(e); try { xPathFactory = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI, "net.sf.saxon.xpath.XPathFactoryImpl", ClassLoader.getSystemClassLoader()); } catch (Throwable e2) { System.err.println(e); try { xPathFactory = XPathFactory.newInstance(); } catch (Exception e3) { e3.printStackTrace(); } } } } public static void setXPathFunctionResolver(XPathFunctionResolver xPathFunctionResolver) { xPathFactory.setXPathFunctionResolver(xPathFunctionResolver); } public static Node selectSingleNode(Node node, String path) throws Exception { return selectSingleNode(node, path, null); } public static boolean evaluate(Node node,String path) throws Exception { javax.xml.xpath.XPath xPath = xPathFactory.newXPath(); NamespaceContextMap namespaceContextMap = new NamespaceContextMap(); namespaceContextMap.addNamespace("server", CapoApplication.SERVER_NAMESPACE_URI); namespaceContextMap.addNamespace("client", CapoApplication.CLIENT_NAMESPACE_URI); xPath.setNamespaceContext(namespaceContextMap); //String parsedXpath = processFunctions(path,prefix); XPathExpression xPathExpression = xPath.compile(path); return (Boolean) xPathExpression.evaluate(node,XPathConstants.BOOLEAN); } public static Node selectSingleNode(Node node, String path,String prefix) throws Exception { try { //dumpNode(node.getOwnerDocument(), System.err); javax.xml.xpath.XPath xPath = xPathFactory.newXPath(); NamespaceContextMap namespaceContextMap = new NamespaceContextMap(); namespaceContextMap.addNamespace("server", CapoApplication.SERVER_NAMESPACE_URI); namespaceContextMap.addNamespace("client", CapoApplication.CLIENT_NAMESPACE_URI); namespaceContextMap.addNamespace("resource", CapoApplication.RESOURCE_NAMESPACE_URI); xPath.setNamespaceContext(namespaceContextMap); //String parsedXpath = processFunctions(path,prefix); XPathExpression xPathExpression = xPath.compile(path); return (Node) xPathExpression.evaluate(node,XPathConstants.NODE); } catch (Exception exception) { CapoServer.logger.log(Level.SEVERE, "Error evaluating '"+path+"' on "+getPathToRoot(node)); throw exception; } } public static NodeList selectNodes(Node node, String path) throws Exception { return selectNodes(node, path, null); } public static Node selectNSNode(Node node, String path,String... namespaces) throws Exception { try { javax.xml.xpath.XPath xPath = xPathFactory.newXPath(); NamespaceContextMap namespaceContextMap = new NamespaceContextMap(); for (String namespace : namespaces) { String[] namespaceDecl = namespace.split("="); namespaceContextMap.addNamespace(namespaceDecl[0], namespaceDecl[1]); } xPath.setNamespaceContext(namespaceContextMap); XPathExpression xPathExpression = xPath.compile(path); return (Node) xPathExpression.evaluate(node,XPathConstants.NODE); } catch (Exception exception) { if (CapoServer.logger != null) { CapoServer.logger.log(Level.SEVERE, "Error evaluating xpath '"+path+"' on "+getPathToRoot(node)); } throw exception; } } public static NodeList selectNSNodes(Node node, String path,String... namespaces) throws Exception { try { javax.xml.xpath.XPath xPath = xPathFactory.newXPath(); NamespaceContextMap namespaceContextMap = new NamespaceContextMap(); for (String namespace : namespaces) { String[] namespaceDecl = namespace.split("="); namespaceContextMap.addNamespace(namespaceDecl[0], namespaceDecl[1]); } xPath.setNamespaceContext(namespaceContextMap); XPathExpression xPathExpression = xPath.compile(path); return (NodeList) xPathExpression.evaluate(node,XPathConstants.NODESET); } catch (Exception exception) { if (CapoServer.logger != null) { CapoServer.logger.log(Level.SEVERE, "Error evaluating xpath '"+path+"' on "+getPathToRoot(node)); } throw exception; } } public static void removeNamespaceDeclarations(Node node,String... namespaces) { NamedNodeMap namedNodeMap = ((Element)node).getAttributes(); for(int nameIndex = 0; nameIndex < namedNodeMap.getLength(); nameIndex++) { Node namedNode = namedNodeMap.item(nameIndex); String uri = namedNode.getNamespaceURI(); String localName = namedNode.getLocalName(); if (uri != null && uri.equals("http://www.w3.org/2000/xmlns/")) { for (String removeableNamespace : namespaces) { if (namedNode.getNodeValue().equals(removeableNamespace)) { ((Element)node).removeAttributeNS("http://www.w3.org/2000/xmlns/",localName); nameIndex--; } } } } } public static NodeList selectNodes(Node node, String path,String prefix) throws Exception { try { javax.xml.xpath.XPath xPath = xPathFactory.newXPath(); NamespaceContextMap namespaceContextMap = new NamespaceContextMap(); namespaceContextMap.addNamespace("server", CapoApplication.SERVER_NAMESPACE_URI); namespaceContextMap.addNamespace("client", CapoApplication.CLIENT_NAMESPACE_URI); namespaceContextMap.addNamespace("resource", CapoApplication.RESOURCE_NAMESPACE_URI); xPath.setNamespaceContext(namespaceContextMap); XPathExpression xPathExpression = xPath.compile(path); return (NodeList) xPathExpression.evaluate(node,XPathConstants.NODESET); } catch (Exception exception) { exception.printStackTrace(); if (CapoServer.logger != null) //TODO this shouldn't have a dependency on CapoServer! { CapoServer.logger.log(Level.SEVERE, "Error evaluating xpath '"+path+"' on "+getPathToRoot(node)); } throw exception; } } public static String selectSingleNodeValue(Element node, String path,String... namespaces) throws Exception { return selectSingleNodeValue(node, path, null,namespaces); } public static String selectSingleNodeValue(Element node, String path, String prefix,String... namespaces) throws Exception { try { javax.xml.xpath.XPath xPath = xPathFactory.newXPath(); NamespaceContextMap namespaceContextMap = new NamespaceContextMap(); namespaceContextMap.addNamespace("server", CapoApplication.SERVER_NAMESPACE_URI); namespaceContextMap.addNamespace("client", CapoApplication.CLIENT_NAMESPACE_URI); namespaceContextMap.addNamespace("resource", CapoApplication.RESOURCE_NAMESPACE_URI); for (String namespace : namespaces) { String[] namespaceDecl = namespace.split("="); namespaceContextMap.addNamespace(namespaceDecl[0], namespaceDecl[1]); } xPath.setNamespaceContext(namespaceContextMap); XPathExpression xPathExpression = xPath.compile(path); return xPathExpression.evaluate(node); } catch (Exception exception) { exception.printStackTrace(); //keep this from looping out of control when things are weird if (exception.getCause() == null || exception.getCause().getMessage() == null) { CapoServer.logger.log(Level.SEVERE, "Error evaluating xpath '"+path+"'"); throw exception; } if (exception.getCause().getMessage().matches(".*context.*item.*for.*axis.*step.*is.*undefined.*")) { CapoServer.logger.log(Level.SEVERE, "Error evaluating xpath '"+path+"'"); throw exception; } CapoServer.logger.log(Level.SEVERE, "Error evaluating xpath '"+path+"' on "+getPathToRoot(node)); throw exception; } } public static String getXPath(Node node) throws Exception { //example // /server:Capo/server:group[1]/server:choose[1]/server:when[1] return getPathToRoot(node); } private static String getPathToRoot(Node node) throws Exception { if(node.getOwnerDocument() == null) { Document tempDocument = new CDocument(); node = tempDocument.adoptNode(node.cloneNode(true)); } String name = node.getNodeName(); if (node instanceof Element) { String nameAttributeValue = ((Element) node).getAttribute("name"); //if (nameAttributeValue.isEmpty() == true ) { if (node.getParentNode() != null) { String[] nameSpace = new String[0]; if(node.getNamespaceURI() != null) { nameSpace = new String[]{node.getPrefix()+"="+node.getNamespaceURI()}; } //if we're in the default namespace, makeup a prefix aka 'null:' in this current case String position = selectSingleNodeValue((Element) node, "count(preceding-sibling::"+(node.getPrefix() == null && node.getNamespaceURI() != null? node.getPrefix()+":" :"")+name+")+1",nameSpace); name += "["+position+"]"; } else //this node does not belong to a document. It must have just been created. { name += "[ORPHAN_NODE]"; } } if(nameAttributeValue.isEmpty() == false) { name += "[@name = '"+nameAttributeValue+"']"; } } if(node instanceof Attr) { name = "@"+name; } if (node.getParentNode() != null && node.getParentNode().getNodeName() != null && node.getParentNode() instanceof Element) { return getPathToRoot(node.getParentNode())+"/"+name; } else { return "/"+name; } } // private static String processFunctions(String varString,String prefix) // { // StringBuffer stringBuffer = new StringBuffer(varString); // processFunctions(stringBuffer,prefix); // return stringBuffer.toString(); // } // private static void processFunctions(StringBuffer varStringBuffer,String prefix) // { // if (prefix == null) // { // prefix = ""; // } // else // { // prefix = prefix +":"; // } // // Set<String> keySet = xpathFunctionProcessorHashMap.keySet(); // // for (String functionName : keySet) // { // //TODO deal with nested functions and quotes in quotes // //TODO look into cache this matches function, since it's got the greatest number of allocations in the system. // if (varStringBuffer != null) // { // String varString = varStringBuffer.toString(); // if( varString.matches(".*"+functionName.toString()+functionMatcher)) // { // String argument = varString.replaceFirst(".*"+functionName.toString()+functionMatcher, "$1"); // String originalFunctionDeclaration = functionName.toString()+"("+argument+")"; // //strip of any quotes // argument = argument.replaceAll("'", ""); // int startIndex = varStringBuffer.indexOf(originalFunctionDeclaration); // String[] arguments = argument.split(","); // // String value = xpathFunctionProcessorHashMap.get(functionName).processFunction(prefix, functionName, arguments); // // varStringBuffer.replace(startIndex, startIndex+originalFunctionDeclaration.length(), value); // } // } // } // // if (CapoServer.logger != null) // { // CapoServer.logger.log(Level.FINE,"final function replacement = '"+varStringBuffer+"'"); // } // // } public static void dumpNode(Node node, OutputStream outputStream) throws Exception { TransformerFactory tFactory = TransformerFactory.newInstance(); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document indentityTransforDocument = documentBuilder.parse(XPath.class.getClassLoader().getResourceAsStream("defaults/identity_transform.xsl")); Transformer transformer = tFactory.newTransformer(new DOMSource(indentityTransforDocument)); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //transformer.setOutputProperty(SaxonOutputKeys.INDENT_SPACES,"4"); // if(node.getOwnerDocument() == null) // { // Document tempDocument = documentBuilder.newDocument(); // node = tempDocument.adoptNode(node.cloneNode(true)); // } transformer.transform(new DOMSource(node), new StreamResult(outputStream)); if(outputStream == System.out || outputStream == System.err) { outputStream.write(new String("\n").getBytes()); } } /** * * @param node * @param xpath * @return list of nodes removed * @throws Exception */ public static NodeList removeNodes(Node node, String xpath) throws Exception { NodeList nodeList = selectNodes(node, xpath); for (int nodeListIndex = 0; nodeListIndex < nodeList.getLength(); nodeListIndex++) { Node removeableNode = nodeList.item(nodeListIndex); Node parentNode = removeableNode.getParentNode(); if (parentNode != null) { parentNode.removeChild(removeableNode); } } return nodeList; } /** * * @param wrappedDocument * @param useAdoption - controls whether or not we can use the quicker adoption method, as opposed to making a copy of the document first. * @return document with first root elements child as document element of new document. * @throws Exception */ public static Document unwrapDocument(Document wrappedDocument,boolean useAdoption) throws Exception { //unwrap document Document unwrappedDocument = CapoApplication.getDocumentBuilder().newDocument(); NodeList nodeList = wrappedDocument.getDocumentElement().getElementsByTagName("*"); if (nodeList.getLength() != 0) { if(useAdoption == false) { unwrappedDocument.appendChild(unwrappedDocument.importNode(nodeList.item(0),true)); } else { unwrappedDocument.appendChild(unwrappedDocument.adoptNode(nodeList.item(0))); } } else { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); XPath.dumpNode(wrappedDocument, byteArrayOutputStream); throw new Exception("No root element child found:"+new String(byteArrayOutputStream.toByteArray())); } return unwrappedDocument; } public static Document wrapDocument(Document parentDocument, Document childDocument) throws Exception { parentDocument.getDocumentElement().appendChild(parentDocument.importNode(childDocument.getDocumentElement(), true)); return parentDocument; } public static void removeContent(Element element) { NodeList nodeList = element.getChildNodes(); while(nodeList.getLength() > 0) { element.removeChild(nodeList.item(0)); } } public static void removeAttributes(Element element) { NamedNodeMap namedNodeMap = element.getAttributes(); while (namedNodeMap.getLength() > 0) { element.removeAttributeNode((Attr) namedNodeMap.item(0)); } } public static String getElementMD5(Element element) throws Exception { String md5 = ""; MD5FilterOutputStream md5FilterOutputStream = new MD5FilterOutputStream(new NullOutputStream()); getElementMD5(element, md5FilterOutputStream); md5FilterOutputStream.close(); md5 = md5FilterOutputStream.getMD5(); return md5; } private static void getElementMD5(Element element, MD5FilterOutputStream md5FilterOutputStream) throws Exception { //process attributes first NamedNodeMap attributeList = element.getAttributes(); for (int currentAttribute = 0; currentAttribute < attributeList.getLength(); currentAttribute++) { Node attributeNode = attributeList.item(currentAttribute); if (attributeNode instanceof Attr) { Attr attr = (Attr) attributeNode; md5FilterOutputStream.write(attr.getName()); md5FilterOutputStream.write(attr.getValue()); } } //then process children NodeList nodeList = element.getChildNodes(); for (int currentNode = 0; currentNode < nodeList.getLength(); currentNode++) { Node node = nodeList.item(currentNode); if (node instanceof Element) { getElementMD5((Element) node, md5FilterOutputStream); } else if (node instanceof Text) { if (node.getNodeValue().trim().isEmpty() == false) { md5FilterOutputStream.write(node.getNodeValue()); } } } } }