// Loosely Coupled SOAP/WSDL Client in Java // Author: Dion Hinchcliffe // Date: June 20th, 2005 // // Usage: oc OrderID Name Amount Price DeliveryDate // Description: Will check the contract at the WSDL URL and invoke the placeOrder operation described therein, // then wait ten (10) seconds and retrieve the order and print the order to the console // Note: Only the location, binding, operations, and dependent schemas are checked // // IMPORTANT: Requires Java 1.5 (5) to compile and run due to need for XPath (which is not in 1.4 or older) import java.net.*; import java.io.*; import java.util.*; import javax.xml.xpath.*; import javax.xml.parsers.*; import javax.xml.namespace.*; import org.w3c.dom.*; public class oc { public static void main(String[] args) { // Check the number of parameters and print usage if needed if (args.length != 6) { System.out.println("Order Client Usage: oc OrderID Name Amount Price DeliveryDate"); return; } try { // Fetch the WSDL at the URL provided System.out.println("Get the Machine Readable Service Description"); // We have the service description XML text, now parse it DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); DocumentBuilder parser = dbf.newDocumentBuilder(); Document doc = parser.parse(args[0]); // GETs the WSDL from the server URL via HTTP XPath xpath = XPathFactory.newInstance().newXPath(); // Add namespace support so that our XPath queries will work in Java ocNamespaceContext nsc = new ocNamespaceContext(); nsc.setNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/"); nsc.setNamespace("soap", "http://schemas.xmlsoap.org/wsdl/soap/"); nsc.setNamespace("s", "http://www.w3.org/2001/XMLSchema"); xpath.setNamespaceContext(nsc); // OK, we want to place an order, and we know it's WSDL, so let's check the contract // Is there an Order Service described? NodeList q = (NodeList) xpath.evaluate("/wsdl:definitions/wsdl:service/@name[.='OrderService']", doc, XPathConstants.NODESET); if (q.getLength() == 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: No OrderService present"); return; } // The service is there, so lets check out the particulars, like if there's a SOAP binding q = (NodeList) xpath.evaluate("/wsdl:definitions/wsdl:service/wsdl:port[@name='OrderServiceSoap' and @binding='tns:OrderServiceSoap']", doc, XPathConstants.NODESET); if (q.getLength() == 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: No SOAP binding"); return; } // There is a SOAP binding for OrderService, verify the details including the operations q = (NodeList) xpath.evaluate("/wsdl:definitions/wsdl:binding[@name='OrderServiceSoap' and @type='tns:OrderServiceSoap' and " + "soap:binding[@transport = 'http://schemas.xmlsoap.org/soap/http' and @style = 'document'] and " + "wsdl:operation[@name = 'placeOrder' and soap:operation[@soapAction = 'http://www.hinchcliffe.org/placeOrder']] and "+ "wsdl:operation[@name = 'getOrderStatus' and soap:operation[@soapAction = 'http://www.hinchcliffe.org/getOrderStatus']]]", doc, XPathConstants.NODESET); if (q.getLength() == 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: Check of the OrderService binding failed"); return; } // OK, do we actually have SOAP ports for the operations we want q = (NodeList) xpath.evaluate("/wsdl:definitions/wsdl:portType[@name='OrderServiceSoap' and " + "wsdl:operation[@name='placeOrder' and wsdl:input[@message='tns:placeOrderSoapIn'] and wsdl:output[@message='tns:placeOrderSoapOut']] and " + "wsdl:operation[@name='getOrderStatus' and wsdl:input[@message='tns:getOrderStatusSoapIn'] and wsdl:output[@message='tns:getOrderStatusSoapOut']]]", doc, XPathConstants.NODESET); if (q.getLength() == 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: No placeOrder() operation binding"); return; } // Now, let's check the input and output schemas of the SOAP messages, and if it all // matches, it's a wrap: the service contract is good // Step 1: Make sure the placeOrder() operation takes an order schema q = (NodeList) xpath.evaluate("/wsdl:definitions/wsdl:types/s:schema[@targetNamespace='http://www.hinchcliffe.org/']/s:element[@name='placeOrder']/s:complexType/s:sequence/s:element[@minOccurs='1' and @ref='order']", doc, XPathConstants.NODESET); if (q.getLength() == 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: placeOrder() doesn't take an order structure"); return; } // Step 2: Make sure the order structure still has the fields we need and their types haven't changed q = (NodeList) xpath.evaluate("/wsdl:definitions/wsdl:types/s:schema/s:complexType[@name='Order']/s:sequence"+ "[s:element[@name = 'orderID' and @type='s:int'] and " + "s:element[@name = 'name' and @type='s:string'] and " + "s:element[@name = 'amount' and @type='s:int'] and " + "s:element[@name = 'price' and @type='s:double'] and " + "s:element[@name = 'deliverydate' and @type='s:dateTime']]" , doc, XPathConstants.NODESET); if (q.getLength() == 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: order structure doesn't have fields required for nominal operation"); return; } // Step 3a: Last, check the in-sies and outies of getOrderStatus() since we need to make sure we get an order structure back q = (NodeList) xpath.evaluate("/wsdl:definitions/wsdl:types/s:schema/s:element[@name='getOrderStatus']/s:complexType/s:sequence/s:element[@minOccurs='0' and @name='oid' and @type='s:string']" , doc, XPathConstants.NODESET); if (q.getLength() == 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: getOrderStatus doesn't take at least a string parameter"); return; } // Step 3b: Does getOrderStatus response at least return an order structure q = (NodeList) xpath.evaluate("/wsdl:definitions/wsdl:types/s:schema/s:element[@name='getOrderStatusResponse']/s:complexType/s:sequence/s:element[@minOccurs='1' and @ref='getOrderStatusResult']" , doc, XPathConstants.NODESET); if (q.getLength() == 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: getOrderStatus doesn't return the structure type expected"); return; } // Step 3c: Does getOrderStatus response actually have the type of 'order'? q = (NodeList) xpath.evaluate("/wsdl:definitions/wsdl:types/s:schema/s:element[@name='getOrderStatusResult' and @type='Order']", doc, XPathConstants.NODESET); if (q.getLength() == 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: getOrderStatus doesn't have the right structure mapped to the result"); return; } // mustUnderstand present? If there is any in this service, then we can't use it because the contract we agreed to didnt' have it q = (NodeList) xpath.evaluate("/wsdl:definitions//@mustUnderstand[.='1']", doc, XPathConstants.NODESET); if (q.getLength() != 0) { System.err.println("UNACCEPTABLE CONTRACT CHANGE: A mustUnderstand header has been added to the contract, which we can't handle"); return; } // Checking is done, build the message according to the contractual format and send it String soapOrderRequest = ""+ ""+ ""+ ""+ ""+ ""+ args[1] + ""+ ""+ args[2] + ""+ ""+ args[3] + ""+ ""+args[4]+""+ ""+args[5]+""+ ""+ ""+ ""+ ""; // OK, build the POST request to send the order, and POST it String pr = ""; URI uri = new URI("http://hinchcliffe.org/order.asmx"); String protocol = uri.getScheme(); String host = uri.getHost(); String path = uri.getRawPath(); String query = uri.getRawQuery(); if (query != null && query.length() > 0) { path+= "?"+query; } int port = uri.getPort(); Socket socket = new Socket(host, 80); InputStream fromServer = socket.getInputStream(); PrintWriter toServer = new PrintWriter(socket.getOutputStream()); toServer.print("POST "+path+" HTTP/1.0\r\n"+ "Host: "+host+"\r\n" + "Content-Type: text/xml; charset=utf-8\r\n" + "SOAPAction: http://www.hinchcliffe.org/placeOrder\r\n" + "Content-Length: "+soapOrderRequest.length() + "\r\n" + "Connection: close\r\n\r\n"+ soapOrderRequest); toServer.flush(); // Issue the POST immediately byte[] buffer = new byte[64 * 1024]; int bytes_read = 0; // Read the headers int numbytes = 0; while(true) { bytes_read = fromServer.read(buffer, numbytes, buffer.length-numbytes); if (bytes_read == -1) break; numbytes += bytes_read; if (numbytes >= (64*1024)) break; } // Find end of headers int i = 0; while (i<= numbytes - 4) { if (buffer[i++] == 13 && buffer[i++] == 10 && buffer[i++] ==13 && buffer[i++] == 10) break; } // Pull the response from POST pr = new String(buffer,i, numbytes-i); socket.close(); System.out.println("Request complete, checking for SOAP faults"); // Check for success Document por = parser.parse(new StringBufferInputStream(pr)); // por = Place Order Response // Make sure we have the WSDL namespace declared q = (NodeList) xpath.evaluate("//soap:Fault" , por, XPathConstants.NODESET); if (q.getLength() != 0) { System.out.println("SOAP FAULT: "+pr); return; } else { System.out.println("SUCCESS!"); } // Wait 10 seconds // Build the getOrderStatus SOAP request to pull the order we just placed soapOrderRequest = ""+ ""+ ""+ ""+ ""+args[1]+""+ ""+ ""+ ""; // OK, build the POST request to retrieve the order we just placed pr = ""; uri = new URI("http://hinchcliffe.org/order.asmx"); protocol = uri.getScheme(); host = uri.getHost(); path = uri.getRawPath(); query = uri.getRawQuery(); if (query != null && query.length() > 0) { path+= "?"+query; } port = uri.getPort(); socket = new Socket(host, 80); fromServer = socket.getInputStream(); toServer = new PrintWriter(socket.getOutputStream()); toServer.print("POST "+path+" HTTP/1.0\r\n"+ "Host: "+host+"\r\n" + "Content-Type: text/xml; charset=utf-8\r\n" + "SOAPAction: http://www.hinchcliffe.org/getOrderStatus\r\n" + "Content-Length: "+soapOrderRequest.length() + "\r\n" + "Connection: close\r\n\r\n"+ soapOrderRequest); toServer.flush(); // Issue request immediately buffer = new byte[64 * 1024]; bytes_read = 0; // Read the headers numbytes = 0; while(true) { bytes_read = fromServer.read(buffer, numbytes, buffer.length-numbytes); if (bytes_read == -1) break; numbytes += bytes_read; if (numbytes >= (64*1024)) break; } // Find end of headers i = 0; while (i<= numbytes - 4) { if (buffer[i++] == 13 && buffer[i++] == 10 && buffer[i++] ==13 && buffer[i++] == 10) break; } // Pull the response from POST pr = new String(buffer,i, numbytes-i); socket.close(); System.out.println("Request complete, checking for SOAP faults"); // Check for success Document gor = parser.parse(new StringBufferInputStream(pr)); // gor = Get Order Response // Make sure we have the WSDL namespace declared q = (NodeList) xpath.evaluate("//soap:Fault" , por, XPathConstants.NODESET); if (q.getLength() != 0) { System.out.println("SOAP FAULT: "+pr); return; } else { System.out.println("SUCCESS!"); System.out.println("Order #"+args[1]+" is "+pr);} } catch (Exception e) { System.err.println(e); e.printStackTrace(); } } // This class is needed so that XPath queries with SOAP/WSDL/XSD namespaces will work properly protected static class ocNamespaceContext implements NamespaceContext { private Map map; public ocNamespaceContext() { map = new HashMap(); } public void setNamespace(String prefix, String namespaceURI) { map.put(prefix, namespaceURI); } public String getNamespaceURI(String prefix) { return (String) map.get(prefix); } public String getPrefix(String namespaceURI) { Set keys = map.keySet(); for (Iterator iterator = keys.iterator(); iterator.hasNext();) { String prefix = (String) iterator.next(); String uri = (String) map.get(prefix); if (uri.equals(namespaceURI)) return prefix; } return null; } public Iterator getPrefixes(String namespaceURI) { List prefixes = new ArrayList(); Set keys = map.keySet(); for (Iterator iterator = keys.iterator(); iterator.hasNext();) { String prefix = (String) iterator.next(); String uri = (String) map.get(prefix); if (uri.equals(namespaceURI)) prefixes.add(prefix); } return prefixes.iterator(); } } // End of NamespaceContext implementation } // End of oc