Java tutorial
// This code is part of camel-soap component. // Thanks to Tom Fennelly from Jboss Group // // Copyright (C) 2009 Wilson Freitas - http://wilsondev.blogspot.com // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA package com.wfreitas.camelsoap; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.naming.ConfigurationException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.stream.StreamResult; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.InputStreamRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.log4j.Logger; import org.milyn.xml.XmlUtil; import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import com.eviware.soapui.impl.wsdl.WsdlInterface; import com.eviware.soapui.impl.wsdl.WsdlProject; import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlLoader; import com.eviware.soapui.model.iface.Operation; import com.wfreitas.camelsoap.util.ClientWsdlLoader; import com.wfreitas.camelsoap.util.DOMUtil; import com.wfreitas.camelsoap.util.OGNLUtils; /** * SoapUI based SOAP client * * @author Wilson Freitas */ public class SoapClient { private DocumentBuilderFactory docBuilderFactory; private Map<String, WsdlInterface[]> wsdls = new HashMap<String, WsdlInterface[]>(); private static final Logger LOGGER = Logger.getLogger(SoapClient.class); private static final String IS_CLONE_ATTRIB = "is-clone"; private static final String CLONED_POSTFIX = " - cloned"; private static final String SOAPUI_CLONE_COMMENT = " repetitions:"; public WsdlInterface[] getWsdlRefeference(String wsdlUrl) throws IOException { return getWsdlInterfaces(wsdlUrl, null); } public SoapClient() { docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setNamespaceAware(true); } //--- PUBLIC - BEGIN public Map sendRequest(String address, String operation, Map params, String wsdlUrl) throws Exception { Operation operationInst = getOperation(wsdlUrl, operation, null); String message = buildRequest(wsdlUrl, operationInst, params, null, null, null); Map responseMap = populateResponseOgnlMap(sendRequest(address, message, operationInst.getAction())); return responseMap; } public String sendRequest(String address, String message, String action) throws Exception { String responseBodyAsString; PostMethod postMethod = new PostMethod(address); try { HttpClient client = new HttpClient(); postMethod.setRequestHeader("SOAPAction", action); postMethod.setRequestEntity( new InputStreamRequestEntity(new ByteArrayInputStream(message.getBytes("UTF-8")), "text/xml") ); client.executeMethod(postMethod); responseBodyAsString = postMethod.getResponseBodyAsString(); } finally { postMethod.releaseConnection(); } return responseBodyAsString; } public String buildRequest(String wsdl, Operation operationInst, Map params, Properties httpClientProps, String smooksResource, String soapNs) throws IOException, UnsupportedOperationException, SAXException { String requestTemplate = operationInst.getRequestAt(0).getRequestContent(); return buildSOAPMessage(requestTemplate, params, smooksResource, soapNs); } //--- PUBLIC - END private Operation getOperation(String wsdl, String operation, Properties httpClientProps) throws IOException, UnsupportedOperationException { WsdlInterface[] wsdlInterfaces = getWsdlInterfaces(wsdl, httpClientProps); for (WsdlInterface wsdlInterface : wsdlInterfaces) { Operation operationInst = wsdlInterface.getOperationByName(operation); if (operationInst != null) { return operationInst; } } wsdls.remove(wsdl); wsdlInterfaces = getWsdlInterfaces(wsdl, httpClientProps); for (WsdlInterface wsdlInterface : wsdlInterfaces) { Operation operationInst = wsdlInterface.getOperationByName(operation); if (operationInst != null) { return operationInst; } } throw new UnsupportedOperationException( "Operation '" + operation + "' not supported by WSDL '" + wsdl + "'."); } private WsdlInterface[] getWsdlInterfaces(String wsdl, Properties httpClientProps) throws IOException { try { WsdlInterface[] wsdlInterfaces = wsdls.get(wsdl); if (wsdlInterfaces == null) { WsdlProject wsdlProject = new WsdlProject(); wsdlInterfaces = wsdlProject.importWsdl(wsdl, true, createWsdlLoader(wsdl, httpClientProps)); wsdls.put(wsdl, wsdlInterfaces); } return wsdlInterfaces; } catch (Exception e) { IOException ioe = new IOException("Failed to import WSDL '" + wsdl + "'."); ioe.initCause(e); throw ioe; } } private WsdlLoader createWsdlLoader(String wsdl, Properties httpClientProps) throws ConfigurationException { HttpClient httpClient = new HttpClient(); return new ClientWsdlLoader(wsdl, httpClient); } //---- SOAP MESSAGE BUILDING private String buildSOAPMessage(String soapMessageTemplate, Map params, String smooksResource, String soapNs) throws IOException, SAXException { Document messageDoc = getDocBuilder().parse(new InputSource(new StringReader(soapMessageTemplate))); Element docRoot = messageDoc.getDocumentElement(); boolean dumpSOAP = params.containsKey("dumpSOAP"); if (dumpSOAP) { dumpSOAP("SOAP Template (Unexpanded):", docRoot); } expandMessage(docRoot, params); if (dumpSOAP) { dumpSOAP("SOAP Template (Expanded):", docRoot); } injectParameters(docRoot, params, soapNs); if (dumpSOAP) { dumpSOAP("SOAP Message (Populated Template):", docRoot); } return XmlUtil.serialize(messageDoc.getChildNodes()); } private synchronized DocumentBuilder getDocBuilder() throws IOException { try { return docBuilderFactory.newDocumentBuilder(); } catch (final ParserConfigurationException pce) { final IOException ioe = new IOException("Could not create document builder"); ioe.initCause(pce); throw ioe; } } private void dumpSOAP(String message, Element docRoot) { System.out.println( "------------------------------------------------------------------------------------------------------------------------------------------"); System.out.println(message + "\n"); try { DOMUtil.serialize(docRoot, new StreamResult(System.out), true); } catch (ConfigurationException e) { LOGGER.error("Unable to dump SOAP.", e); } System.out.println( "------------------------------------------------------------------------------------------------------------------------------------------"); } /** * Expand the message to accommodate data collections. * <p/> * It basically just clones the message where appropriate. * * @param element The element to be processed. * @param params The message params. Uses the message params to * decide whether or not cloning is required. */ private void expandMessage(Element element, Map params) { // If this element is not a cloned element, check does it need to be cloned... if (!element.hasAttributeNS(OGNLUtils.JBOSSESB_SOAP_NS, IS_CLONE_ATTRIB)) { String ognl = OGNLUtils.getOGNLExpression(element); Element clonePoint = getClonePoint(element); if (clonePoint != null) { int collectionSize; collectionSize = calculateCollectionSize(ognl, params); if (collectionSize == -1) { // It's a collection, but has no entries that match the OGNL expression for this element... if (clonePoint == element) { // If the clonePoint is the element itself, we remove it... we're done with it... clonePoint.getParentNode().removeChild(clonePoint); } else { // If the clonePoint is not the element itself (it's a child element), leave it // and check it again when we get to it... resetClonePoint(clonePoint); } } else if (collectionSize == 0) { // It's a collection, but has no entries, remove it... clonePoint.getParentNode().removeChild(clonePoint); } else if (collectionSize == 1) { // It's a collection, but no need to clone coz we // already have an entry for it... clonePoint.setAttributeNS(OGNLUtils.JBOSSESB_SOAP_NS, OGNLUtils.JBOSSESB_SOAP_NS_PREFIX + OGNLUtils.OGNL_ATTRIB, ognl + "[0]"); } else { // It's a collection and we need to do some cloning if (clonePoint != null) { // We already have one, so decrement by one... cloneCollectionTemplateElement(clonePoint, (collectionSize - 1), ognl); } else { LOGGER.warn("Collection/array template element <" + element.getLocalName() + "> would appear to be invalid. It doesn't contain any child elements."); } } } } // Now do the same for the child elements... List<Node> children = DOMUtil.copyNodeList(element.getChildNodes()); for (Node node : children) { if (node.getNodeType() == Node.ELEMENT_NODE) { expandMessage((Element) node, params); } } } private Element getClonePoint(Element element) { Comment comment; // Is it this element... comment = getCommentBefore(element); if (comment != null && comment.getNodeValue().endsWith(SOAPUI_CLONE_COMMENT)) { comment.setNodeValue(comment.getNodeValue() + CLONED_POSTFIX); return element; } // Is it the first child element of this element... Element firstChildElement = getFirstChildElement(element); if (firstChildElement != null) { comment = getCommentBefore(firstChildElement); if (comment != null && comment.getNodeValue().endsWith(SOAPUI_CLONE_COMMENT)) { comment.setNodeValue(comment.getNodeValue() + CLONED_POSTFIX); return firstChildElement; } } return null; } private Comment getCommentBefore(Element element) { Node sibling = element.getPreviousSibling(); while (sibling != null) { if (sibling.getNodeType() == Node.COMMENT_NODE) { return (Comment) sibling; } else if (sibling.getNodeType() == Node.TEXT_NODE) { // continue... sibling = sibling.getPreviousSibling(); } else { // It's an Element, CData, PI etc return null; } } return null; } private Element getFirstChildElement(Element element) { NodeList children = element.getChildNodes(); int childCount = children.getLength(); for (int i = 0; i < childCount; i++) { Node child = children.item(i); if (child.getNodeType() == Node.ELEMENT_NODE) { return (Element) child; } } return null; } private int calculateCollectionSize(String ognl, Map params) { // Try for an Object graph based collection... Object param = OGNLUtils.getParameter(ognl, params); if (param != null) { Class paramRuntime = param.getClass(); if (paramRuntime.isArray()) { return ((Object[]) param).length; } else if (Collection.class.isAssignableFrom(paramRuntime)) { return ((Collection) param).size(); } } // Try for <String, Object> collection based map entries... Set<Map.Entry> entries = params.entrySet(); String collectionPrefix = ognl + "["; int maxIndex = -1; for (Map.Entry entry : entries) { Object keyObj = entry.getKey(); if (keyObj instanceof String) { String key = (String) keyObj; if (key.startsWith(collectionPrefix)) { int endIndex = key.indexOf(']', collectionPrefix.length()); String ognlIndexValString = key.substring(collectionPrefix.length(), endIndex); try { int ognlIndexVal = Integer.valueOf(ognlIndexValString); maxIndex = Math.max(maxIndex, ognlIndexVal); } catch (NumberFormatException e) { } } } } if (maxIndex != -1) { return maxIndex + 1; } // It's a collection, but nothing in this message for it collection... return -1; } private void resetClonePoint(Element clonePoint) { Comment comment = getCommentBefore(clonePoint); if (comment == null) { throw new IllegalStateException("Call to reset a 'clonePoint' that doesn't have a comment before it."); } String commentText = comment.getNodeValue(); if (!commentText.endsWith(CLONED_POSTFIX)) { throw new IllegalStateException( "Call to reset a 'clonePoint' that doesn't have a proper clone comment before it."); } comment.setNodeValue(commentText.substring(0, commentText.length() - CLONED_POSTFIX.length())); } /** * Clone a collection node. * <p/> * Note we have to frig with the OGNL expressions for collections/arrays because the * collection entry is represented by [0], [1] etc in the OGNL expression, not the actual * element name on the DOM e.g. collection node "order/items/item" (where "item" is the * actual collection entry) maps to the OGNL expression "order.items[0]" etc. * * @param element The collection/array "entry" sub-branch. * @param cloneCount The number of times it needs to be cloned. * @param ognl The OGNL expression for the collection/array. Not including the * indexing part. */ private void cloneCollectionTemplateElement(Element element, int cloneCount, String ognl) { if (element == null) { return; } Node insertPoint = element.getNextSibling(); Node parent = element.getParentNode(); element.setAttributeNS(OGNLUtils.JBOSSESB_SOAP_NS, OGNLUtils.JBOSSESB_SOAP_NS_PREFIX + OGNLUtils.OGNL_ATTRIB, ognl + "[0]"); for (int i = 0; i < cloneCount; i++) { Element clone = (Element) element.cloneNode(true); clone.setAttributeNS(OGNLUtils.JBOSSESB_SOAP_NS, OGNLUtils.JBOSSESB_SOAP_NS_PREFIX + IS_CLONE_ATTRIB, "true"); clone.setAttributeNS(OGNLUtils.JBOSSESB_SOAP_NS, OGNLUtils.JBOSSESB_SOAP_NS_PREFIX + OGNLUtils.OGNL_ATTRIB, ognl + "[" + Integer.toString(i + 1) + "]"); if (insertPoint == null) { parent.appendChild(clone); } else { parent.insertBefore(clone, insertPoint); } } } private void injectParameters(Element element, Map params, String soapNs) { NodeList children = element.getChildNodes(); int childCount = children.getLength(); for (int i = 0; i < childCount; i++) { Node node = children.item(i); if (childCount == 1 && node.getNodeType() == Node.TEXT_NODE) { if (node.getNodeValue().equals("?")) { String ognl = OGNLUtils.getOGNLExpression(element, soapNs); Object param; param = OGNLUtils.getParameter(ognl, params); element.removeChild(node); element.appendChild(element.getOwnerDocument().createTextNode(param.toString())); } } else if (node.getNodeType() == Node.ELEMENT_NODE) { injectParameters((Element) node, params, soapNs); } } element.removeAttributeNS(OGNLUtils.JBOSSESB_SOAP_NS, IS_CLONE_ATTRIB); element.removeAttributeNS(OGNLUtils.JBOSSESB_SOAP_NS, OGNLUtils.OGNL_ATTRIB); } private Map<String, String> populateResponseOgnlMap(String response) { Map<String, String> map = new LinkedHashMap<String, String>(); try { DocumentBuilder docBuilder = getDocBuilder(); Document doc = docBuilder.parse(new InputSource(new StringReader(response))); Element graphRootElement = getGraphRootElement(doc.getDocumentElement()); populateResponseOgnlMap(map, graphRootElement); } catch (SAXException e) { throw new RuntimeException("Error parsing SOAP response.", e); } catch (IOException e) { throw new RuntimeException("Unexpected error reading SOAP response.", e); } return map; } private void populateResponseOgnlMap(Map<String, String> map, Element element) { NodeList children = element.getChildNodes(); int childCount = children.getLength(); // If the element has a solitary TEXT child, add the text value // against a map key of the elements OGNL expression value. if (childCount == 1) { Node childNode = children.item(0); if (childNode.getNodeType() == Node.TEXT_NODE) { String ognl = OGNLUtils.getOGNLExpression(element); map.put(ognl, childNode.getNodeValue()); return; } } // So the element doesn't contain a solitary TEXT node. Drill down... for (int i = 0; i < childCount; i++) { Node childNode = children.item(i); if (childNode.getNodeType() == Node.ELEMENT_NODE) { populateResponseOgnlMap(map, (Element) childNode); } } } private Element getGraphRootElement(Element element) { String ognl = OGNLUtils.getOGNLExpression(element); if (ognl != null && !ognl.equals("")) { return element; } NodeList children = element.getChildNodes(); int childCount = children.getLength(); for (int i = 0; i < childCount; i++) { Node node = children.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { Element graphRootElement = getGraphRootElement((Element) node); if (graphRootElement != null) { return graphRootElement; } } } return null; } }