/**
* 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));
}
}