Apache SOAP
The Open Source Apache SOAP toolkit is a Java package that can be downloaded for free (subject to the Apache license) from the Apache Software Foundation (see http://xml.apache.org/soap/index.html). This library is used in conjunction with the Apache Xerces XML parser and is contained within the org.apache.soap package. Features of the Apache SOAP library include support for HTTP and SMTP transports, scripting languages, Java object serialization, and SOAP Messages with Attachments (currently a W3C Note). Unlike the Microsoft SOAP Toolkit, Apache SOAP doesn't currently natively support WDSL descriptions of a web service. Instead, Apache SOAP makes use of "deployment descriptor" XML files to "provide information to the SOAP runtime about the services that should be made available to clients." I built a simple example that simply allows a client to retrieve a list of all Major League Baseball teams that are likely to have a chance to make the playoffs as I write this.
To start with, I defined a deployment descriptor XML file that defines the services to be deployed by this application:
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:BaseballSeason"> <isd:provider type="java" scope="Application" methods="getAllTeams"> <isd:java class="BaseballInfo" static="false"/> </isd:provider> <isd:faultListener> org.apache.soap.server.DOMFaultListener </isd:faultListener> </isd:service>
The important items to take away from this descriptor are the following:
The application name URN (BaseballSeason).
The implementation class (BaseballInfo).
The methods supported by this SOAP service (getAllTeams). If there was one more method, the methods parameter would be populated with a comma-delimited, case-sensitive list of method names to be called.
The following code listing shows the BaseballInfo class (along with the Team internal class). It uses a Java Hashtable object to store all baseball team info (you could just as easily use a relational database with JDBC). The getAllTeams() method returns a Xerces XML Element class, meaning that the list of teams is returned as formatted XML to be parsed on the client.
import java.util.*; import org.w3c.dom.*; import javax.xml.parsers.*; import org.apache.soap.util.xml.*; public class BaseballInfo { private Hashtable teamTable = new Hashtable(); public BaseballInfo() { teamTable.put("NYY", new Team("New York", "Yankees", "American", 76, 54)); teamTable.put("BOS", new Team("Boston", "Red Sox", "American", 71, 57)); teamTable.put("CLE", new Team("Cleveland", "Indians", "American", 72, 57)); teamTable.put("SEA", new Team("Seattle", "Mariners", "American", 94, 36)); teamTable.put("OAK", new Team("Oakland", "A's", "American", 74, 56)); teamTable.put("ATL", new Team("Atlanta", "Braves", "National", 71, 58)); teamTable.put("PHI", new Team("Philadelphia", "Phillies", "National", 69, 60)); teamTable.put("HOU", new Team("Houston", "Astros", "National", 75, 55)); teamTable.put("CHC", new Team("Chicago", "Cubs", "National", 72, 58)); teamTable.put("STL", new Team("St. Louis", "Cardinals", "National", 70, 60)); teamTable.put("ARI", new Team("Arizona", "Diamondbacks", "National", 75, 54)); teamTable.put("SFG", new Team("San Francisco", "Giants", "National", 72, 57)); teamTable.put("LAD", new Team("Los Angeles", "Dodgers", "National", 71, 59)); } public Team getTeamInfo(String code) { return (Team)teamTable.get(code); } public Element getAllTeams() { DocumentBuilder xdb = XMLParserUtils.getXMLDocBuilder(); Document doc = xdb.newDocument(); Element leagueElement = doc.createElement("MajorLeagueBaseball"); leagueElement.appendChild(doc.createTextNode("\n")); for (Enumeration keys = teamTable.keys(); keys.hasMoreElements();) { String code = (String)keys.nextElement(); Team team = (Team)teamTable.get(code); Element teamElement = doc.createElement("Team"); Element codeElement = doc.createElement("Code"); codeElement.appendChild(doc.createTextNode(code)); teamElement.appendChild(doc.createTextNode("\n ")); teamElement.appendChild(codeElement); teamElement.appendChild(doc.createTextNode("\n ")); Element cityElement = doc.createElement("City"); cityElement.appendChild(doc.createTextNode(team.getCity())); teamElement.appendChild(doc.createTextNode("\n ")); teamElement.appendChild(cityElement); teamElement.appendChild(doc.createTextNode("\n ")); Element mascotElement = doc.createElement("Mascot"); mascotElement.appendChild(doc.createTextNode(team.getMascot())); teamElement.appendChild(doc.createTextNode("\n ")); teamElement.appendChild(mascotElement); teamElement.appendChild(doc.createTextNode("\n ")); Element lgElement = doc.createElement("League"); lgElement.appendChild(doc.createTextNode(team.getLeague())); teamElement.appendChild(doc.createTextNode("\n ")); teamElement.appendChild(lgElement); teamElement.appendChild(doc.createTextNode("\n ")); Element winsElement = doc.createElement("Wins"); winsElement.appendChild(doc.createTextNode( new Integer(team.getWins()).toString())); teamElement.appendChild(doc.createTextNode("\n ")); teamElement.appendChild(winsElement); teamElement.appendChild(doc.createTextNode("\n ")); Element lossesElement = doc.createElement("Losses"); lossesElement.appendChild(doc.createTextNode( new Integer(team.getLosses()).toString())); teamElement.appendChild(doc.createTextNode("\n ")); teamElement.appendChild(lossesElement); teamElement.appendChild(doc.createTextNode("\n")); leagueElement.appendChild(teamElement); leagueElement.appendChild(doc.createTextNode("\n")); } System.out.println(DOM2Writer.nodeToString(leagueElement)); return leagueElement; } } public class Team { private String city; private String mascot; private String league; private int wins; private int losses; public Team() { } public Team(String city, String mascot, String league, int wins, int losses) { this.city = city; this.mascot = mascot; this.league = league; this.wins = wins; this.losses = losses; } public int getWins() { return wins; } public int getLosses() { return losses; } public String getCity() { return city; } public String getMascot() { return mascot; } public String getLeague() { return league; } }
Once the server class has been built, it needs to be "registered" on the Apache SOAP server. During my testing, I used the Apache Tomcat server, allowing me to deploy the service using the following command-line operation:
java org.apache.soap.server.ServiceManagerClient [ccc] http://localhost:8080/soap/servlet/rpcrouter [ccc] deploy DeploymentDescriptor.xml
NOTE
This command must be typed on a single line, executed at the command line. We've broken it here to keep it from running off the screen; code-continuation arrows ([ccc]) indicate where the line was broken.
Once deployed, I built a simple test client that simply calls the getAllTeams() SOAP method and prints out the XML that's retrieved. The source code for this client is shown in the following code listing:
import java.io.*; import java.util.*; import java.net.*; import org.w3c.dom.*; import org.apache.soap.util.xml.*; import org.apache.soap.*; import org.apache.soap.encoding.*; import org.apache.soap.encoding.soapenc.*; import org.apache.soap.rpc.*; public class GetAllTeams { public static void main(String[] args) throws Exception { URL url = new URL(args[0]); Call call = new Call(); call.setTargetObjectURI("urn:BaseballSeason"); call.setMethodName("getAllTeams"); call.setEncodingStyleURI(Constants.NS_URI_LITERAL_XML); Response resp; try { resp = call.invoke(url, ""); } catch (SOAPException e) { System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " + e.getMessage()); return; } // Check the response. if (!resp.generatedFault()) { Parameter ret = resp.getReturnValue(); Element teamsElement = (Element)ret.getValue(); System.out.println(DOM2Writer.nodeToString(teamsElement)); } else { Fault fault = resp.getFault(); System.err.println("Generated fault: "); System.out.println (" Fault Code = " + fault.getFaultCode()); System.out.println (" Fault String = " + fault.getFaultString()); } } }
The important classes used in the client are the Call and Response classes. The Call class allows us to set method parameters, while the Response class handles the server response. The teamsElement parameter at the end represents the Xerces Element object passed from the server, allowing us to use another XML parser on the client side to parse this returned value. Of course, this value could have been a simpler datatype, such as an integer or string. While I prefer to abstain from criticizing Open Source productsif you don't like it, change the code!support for a new "service definition" language named WSDL (which actually stands for Web Services Description Language) would be a welcome addition.
About WSDL and UDDI
WSDL was jointly introduced by IBM and Microsoft in 2000. From the specification, "WSDL is an XML format for describing network services as a set of endpoints operating on messages containing either document-oriented or procedure-oriented information." If you define your web service using WSDL, no formal documentation is required. Instead, a WSDL file could be made available on your server and other clients (or servers) could dynamically interact with your offering based on the service description.
WSDL is an important component of a larger effort known as Universal Description, Discovery, and Integration (UDDI)see http://www.uddi.org. UDDI represents the foundation of an online directory whereby web services can both be published and discovered. This general directory is an absolute must (just as web directories such as Yahoo! were very important in the early days of the web). Once your web service is operational (and intended for public use), you can visit a UDDI-compliant directory to make it available for the world to see. Public UDDI directory nodes are currently available from IBM and Microsoft. To locate operational web services using UDDI, visit http://www.soapclient.com/uddisearch.html to use their UDDI browser.