/** * Copyright 2015 Nortal Licensed 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 com.nortal.jroad.util; import java.util.Collection; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathException; import javax.xml.xpath.XPathFactory; import org.springframework.ws.WebServiceMessage; import org.springframework.ws.soap.saaj.SaajSoapMessage; import org.springframework.xml.transform.StringResult; import org.springframework.xml.transform.StringSource; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Helper methods for handling SOAP messages * * @author Dmitri Danilkin * @author Roman Tekhov * @author Lauri Lättemäe (lauri.lattemae@nortal.com) - protocol 4.0 */ public class SOAPUtil { public static final String TEENUS_NS_PREFIX = "tns"; /** * Returns the first child of the parent {@link Node} with the given name, or <code>null</code> if such child is not * found. * * @param root The root node. * @param childName Name of the child node. * @return {@link Node} if a child node was found, <code>null</code> otherwise. */ public static Node getFirstChildByName(Node root, String childName) { Node node = null; try { node = getNodeByXPath(root, "/" + childName); } catch (XPathException e) { // Did not get any node } return node; } /** * Returns the first non-text child node of the given node. * * @param root The {@link Node}, which should be searched. * @return {@link Node} if a child node was found, <code>null</code> if it was not. */ public static Node getFirstNonTextChild(Node root) { Node resultNode = null; NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { if (!isTextNode(nl.item(i))) { resultNode = nl.item(i); break; } } return resultNode; } /** * Evaluates an {@link XPath} expression and returns a <i>single</i> matching node. * * @param context The context in which to run the expression. * @param expression A valid {@link XPath} expression. * @return {@link Node}, if one is found, <code>null</code> otherwise. * @throws XPathException If the provided expression is invalid or multiple nodes match. */ public static Node getNodeByXPath(Object context, String expression) throws XPathException { return (Node) XPathFactory.newInstance().newXPath().evaluate(expression, context, XPathConstants.NODE); } /** * Returns whether given {@link Node} is text {@link Node}. * * @param node the {@link Node} * @return whether given node was text node */ public static boolean isTextNode(Node node) { return node != null ? Node.TEXT_NODE == node.getNodeType() : false; } /** * Returns the text content of a given Node. * * @param node {@link Node} instance * @return text content of a node */ public static String getTextContent(Node node) { if (node == null) { return null; } NodeList nl = node.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node childNode = nl.item(i); if (isTextNode(childNode)) { return childNode.getNodeValue(); } } return null; } public static SOAPMessage extractSoapMessage(WebServiceMessage webServiceMessage) { return ((SaajSoapMessage) webServiceMessage).getSaajMessage(); } /** * Adds base MIME headers to a {@link SOAPMessage}. * * @param message The {@link SOAPMessage} to add the headers to. */ public static void addBaseMimeHeaders(SOAPMessage message) { SOAPUtil.addMimeHeader(message, "Content-Type", "multipart/related"); SOAPUtil.addMimeHeader(message, "SOAPAction", "\"\""); SOAPUtil.addMimeHeader(message, "Accept", "application/soap+xml, application/mime, multipart/related, text/*"); SOAPUtil.addMimeHeader(message, "Cache-Control", "no-cache"); SOAPUtil.addMimeHeader(message, "Pragma", "no-cache"); message.getSOAPPart().setMimeHeader("Content-Type", "text/xml; charset=UTF-8"); message.getSOAPPart().setMimeHeader("Content-Transfer-Encoding", "8bit"); } /** * Adds a custom MIME header to a {@link SOAPMessage}. * * @param message The {@link SOAPMessage} to add the header to. * @param name The name of the header. * @param value The value (content) of the header. */ public static void addMimeHeader(SOAPMessage message, String name, String value) { message.getMimeHeaders().setHeader(name, value); } /** * Adds the base required namespaces (with prefixes <code>xsi</code>, <code>xsd</code>, <code>SOAP-ENC</code>, * <code>SOAP-ENV</code>) to a {@link SOAPMessage} . * * @param message The {@link SOAPMessage} to add the namespaces to. * @throws SOAPException */ public static void addBaseNamespaces(SOAPMessage message) throws SOAPException { SOAPUtil.addNamespace(message, "xsi", "http://www.w3.org/2001/XMLSchema-instance"); SOAPUtil.addNamespace(message, "xsd", "http://www.w3.org/2001/XMLSchema"); SOAPUtil.addNamespace(message, "SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"); SOAPUtil.addNamespace(message, "SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"); } /** * Adds a specified namespace to a {@link SOAPMessage}. * * @param message The {@link SOAPMessage} to add the namespace to. * @param prefix namespace prefix * @param uri namespace URI * @throws SOAPException */ public static void addNamespace(SOAPMessage message, String prefix, String uri) throws SOAPException { message.getSOAPPart().getEnvelope().addNamespaceDeclaration(prefix, uri); } /** * Helper methods for adding an array. */ public static void addArrayAnyTypeAttribute(Element element, Collection<?> col) { addArrayAnyTypeAttribute(element, col.size()); } public static void addArrayAnyTypeAttribute(Element element, int size) { element.setAttribute("SOAP-ENC:arrayType", getAnyTypeAttribute(size)); } public static void addArrayTypeAttribute(Element element, String type, Collection<?> col) { addArrayTypeAttribute(element, type, col.size()); } public static void addArrayTypeAttribute(Element element, String type, int size) { element.setAttribute("SOAP-ENC:arrayType", getArrayTypeAttribute(type, size)); } public static void addArrayOffsetAttribute(Element element, int offset) { element.setAttribute("SOAP-ENC:offset", new StringBuilder("[").append(offset).append("]").toString()); } public static String getAnyTypeAttribute(int size) { return getArrayTypeAttribute("anyType", size); } public static String getArrayTypeAttribute(String type, int size) { return new StringBuilder("xsd:").append(type).append("[").append(size).append("]").toString(); } /** * Adds a type attribute to an element as required by the RPC/Encoded binding. Please note, <code>RPC/Encoded</code> * has been deprecated a very long time ago. This is only used to provide backwards compatibility to "metateenused". * You should never use this in regular services, as <code>RPC/Literal</code> is compatible with * <code>RPC/Encoded</code> parsers. * * @param element The <code>Element</code> to add the type declaration for. * @param type Valid xsi type. */ public static void addTypeAttribute(Element element, String type) { if (type != null) { element.setAttribute("xsi:type", type); } } public static Element addElementInteger(Element element, String id, Long value) throws SOAPException { return addElement(element, id, "xsd:integer", String.valueOf(value)); } public static Element addElementTekst(Element element, String id, String value) throws SOAPException { return addElement(element, id, "xsd:string", value); } /** * Adds a new element to an existing element * * @param element The parent {@link Element}, which the child will be added to. * @param id Tag name of the new {@link Element} * @param type xsi type of the new {@link Element} * @param value Text value of the new {@link Element} * @return * @throws SOAPException */ public static Element addElement(Element element, String id, String type, String value) throws SOAPException { Element child = element.getOwnerDocument().createElement(id); if (value != null) { child.setTextContent(value); } SOAPUtil.addTypeAttribute(child, type); element.appendChild(child); return child; } /** * Substitutes all occurences of some given string inside the given {@link WebServiceMessage} with another value. * * @param message message to substitute in * @param from the value to substitute * @param to the value to substitute with * @throws TransformerException */ public static void substitute(WebServiceMessage message, String from, String to) throws TransformerException { SaajSoapMessage saajSoapMessage = (SaajSoapMessage) message; SOAPPart soapPart = saajSoapMessage.getSaajMessage().getSOAPPart(); Source source = new DOMSource(soapPart); StringResult stringResult = new StringResult(); TransformerFactory.newInstance().newTransformer().transform(source, stringResult); String content = stringResult.toString().replaceAll(from, to); try { soapPart.setContent(new StringSource(content)); } catch (SOAPException e) { throw new TransformerException(e); } } /** * Returns child elements according to name and namespace * * @param root * @param name * @param ns * @return */ public static NodeList getNsElements(Element root, String name, String ns) { if (root == null) { return null; } return root.getElementsByTagNameNS(ns, name); } /** * Returns child element according to name and namespace * * @param root * @param name * @param ns * @return */ public static Element getNsElement(Element root, String name, String ns) { NodeList nl = getNsElements(root, name, ns); if (nl == null || nl.getLength() != 1) { return null; } return (Element) nl.item(0); } /** * Returns child element value according to name and namespace * * @param root * @param name * @param ns * @return */ public static String getNsElementValue(Element root, String name, String ns) { return getTextContent(getNsElement(root, name, ns)); } }