Monkeys at Keyboards: Java-Fu
© Michael James Heron
Topic: Java Programming
Level: 3
Version: beta

I know not, sir, whether Bacon wrote the works of Shakespeare, but if he did not it seems to me that he missed the opportunity of his life.
James Barrie

14 - Web Services

PreviousTable of ContentsNext
Forum


Chapter Objectives

By the end of this chapter, the reader will be able to:



    14.1

    Introduction

    In this chapter we're going to take a look at how to write our own SOAP servers, which are more commonly known as web services. As with several of the most powerful component architectures, web services are entirely implementation independent. SOAP makes use of XML as a truly generic, self-describing data format that allows for clients and servers to be written in any language with no loss of compatibility. Pretty swish!

    SOAP services need to be deployed on a compatible server - in this chapter, we'll be using Tomcat. This should be familiar from the last chapter on servlets. There are further configuration issues that must be resolved before we can productively make use of our very own services - these are however out-with the scope of this particular chapter.

    In this chapter we will be looking at developing SOAP applications in Java, but also taking advantage of the cross-platform implementation to look at equivalent SOAP services using the .NET framework.

    14.2

    My First Web Service

    Okay, we're going to begin our exploration of web services with a simple 'hello world' style service. The code for this is as follows:


    public class MyFirstWebService {

    public String helloWorld() {
    return "Hello World.";
    }

    }

    Egads! Is that it? Surely such a thing cannot be true!

    Your eyes are not deceiving you - that's genuinely how easy it is to write the code for a web-service . It's just a standard class, with no trimmings. Pretty amazing, huh?

    Of course, there's more to it than that - you shouldn't be surprised that it's never quite that simple. However, the complexity does not lie in the code - genuinely, the code above is enough to drive a simple web service.

    The tool that is used to turn a class into a web service is a Web Service Description Language (WSDL) specification - this is a document that details what methods are provided by a service, and what custom XML elements are required to take advantage of those methods. Generating wsdl files is a process that is most usefully automated, and so we will spend only a little time looking at the format.

    WSDL is really just a standard XML document, albeit somewhat more complicated than the ones we may have encountered thus far through the module. As a rule, developers of web services do not write the accompanying WSDL file themselves - they make use of a software tool that does it for them. The WSDL file is essentially a menu that indicates what delicious treats are available within a service, and how they can be ordered. WSDL files are generally very long and boring, and not at all worth us spending our valuable time on. All we want to be able to do is generate one from a given class file. This is a platform dependant task - using Tomcat and Apache Axis makes this a breeze.

    There are two things we need to do to turn our class into a genuine web service. The first is that we need to change the file extension from .java to .jws. The second is we need to copy the file to the appropriate directory of a SOAP enabled server. Once we've done this, we can access the web-service directly.

    Axis allows us to get the wsdl file of our class simply by appending the text ?wsdl to the URL we use to access it. For example:

    http://localhost:8080/axis/MyFirstWebService.jws?wsdl

    The wsdl file generated from our simple service above is as follows:

    <wsdl:definitions targetNamespace="http://localhost:8080/axis/MyFirstWebService.jws">

    <wsdl:message name="helloWorldResponse">
    <wsdl:part name="helloWorldReturn" type="xsd:string"/>
    </wsdl:message>
    <wsdl:message name="helloWorldRequest">
    </wsdl:message>

    <wsdl:portType name="MyFirstWebService">
    <wsdl:operation name="helloWorld">
    <wsdl:input message="impl:helloWorldRequest"
    name="helloWorldRequest"/>
    <wsdl:output message="impl:helloWorldResponse"
    name="helloWorldResponse"/>
    </wsdl:operation>
    </wsdl:portType>

    <wsdl:binding name="MyFirstWebServiceSoapBinding"
    type="impl:MyFirstWebService">

    <wsdlsoap:binding style="rpc"
    transport="http://schemas.xmlsoap.org/soap/http"/>

    <wsdl:operation name="helloWorld">
    <wsdlsoap:operation soapAction=""/>
    <wsdl:input name="helloWorldRequest">
    <wsdlsoap:body
    encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
    namespace="http://DefaultNamespace" use="encoded"/>
    </wsdl:input>

    <wsdl:output name="helloWorldResponse">
    <wsdlsoap:body
    encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
    namespace="http://localhost:8080/axis/MyFirstWebService.jws"
    use="encoded"/>
    </wsdl:output>
    </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="MyFirstWebServiceService">

    <wsdl:port binding="impl:MyFirstWebServiceSoapBinding"
    name="MyFirstWebService">
    <wsdlsoap:address location="http://localhost:8080/axis/MyFirstWebService.jws"/>
    </wsdl:port>

    </wsdl:service>
    </wsdl:definitions>

    Yikes! It's not exactly what you'd call instantly readable, but it specifies all of the information that an interested party would need to make use of our web-service given no other documentation. The wsdl file is language independent, and can be used as the basis for a SOAP client written in any language that provides suitable SOAP compatibility.

    We looked at how to write a SOAP client in a previous chapter - let's go over that once again with particular reference to this new service we've just created.

    First of all, we need to create a new Call object:

    Call myCall = new Call(); 

    Then, we need to call setTargetObjectURI, passing as a parameter the name of the service. The wsdl file above provides us with this information:

    <wsdl:service name="MyFirstWebServiceService">

    So we use MyFirstWebServiceService as the URI to call:

    myCall.setTargetObjectURI ("urn:MyFirstWebServiceService"); 

    We then call setMethodName, choosing the method we wish to call. All available methods are listed as operations in the wsdl file, and in this case, there's just one:

    <wsdl:operation name="helloWorld">

    So this is the value we pass to the appropriate method:

    myCall.setMethodName ("helloWorld"); 

    We have no parameters, so we don't need to worry about creating a vector of Parameter objects... all we need to do is invoke the call, passing as a parameter the location of the web service:

    targetNamespace="http://localhost:8080/axis/MyFirstWebService.jws"

    And so:

    Response res = myCall.invoke (new URL ("http://localhost:8080/axis/MyFirstWebService.jws") 
    , "");

    And voila! With these changes, the SOAP client code we saw in an earlier chapter can be used as the basis of a connection to our brand new service:


    public void makeConnection() {
    Vector params;
    Parameter returnVal;
    Parameter temp;
    String text;
    Response res = null;
    params = new Vector();
    Call myCall = new Call();
    myCall.setTargetObjectURI ("urn:MyFirstWebServiceService");
    myCall.setMethodName ("helloWorld");
    myCall.setEncodingStyleURI (Constants.NS_URI_SOAP_ENC);
    myCall.setParams (params);
    try {
    res = myCall.invoke (new URL ("http://localhost:8080/axis/MyFirstWebService.jws")
    , "");
    }

    catch (MalformedURLException ex) {
    System.out.println ("Bad URL!");
    }

    catch (SOAPException ex) {
    System.out.println ("SOAP Exception.");
    }

    if (res.generatedFault() == true) {
    text = res.getFault() .getFaultString();
    }

    else {
    returnVal = res.getReturnValue();
    text = (String) returnVal.getValue();
    }

    System.out.println ("" + text);
    }

    You might think from this that there really is nothing to writing web-services - and you'd be (almost) right... but this is a very simple example. It doesn't even have any parameters. Let's look at a slightly more complicated service that provides multiple operations. In fact, we're going to write a web-service version of our quote servlet.

    14.3

    A Quote Web Service

    As mentioned in the previous chapter on SOAP, there are a variety of data types that can be sent to and from a SOAP enabled application - even Javabeans. As such, our Quote objects will be setup as Javabeans so that we can send them via HTTP:

    import java.io.*; 

    public class Quote implements Serializable {
    public String author;
    public String quote;

    public Quote() {
    }


    public Quote (String q, String a) {
    setQuote (q);
    setAuthor (a);
    }


    public String getQuote() {
    return quote;
    }


    public String getAuthor() {
    return author;
    }


    public void setQuote (String q) {
    quote = q;
    }


    public void setAuthor (String a) {
    author = a;
    }

    }

    Our server will setup a library of quotes, and then provide a number of methods for providing particular quote objects to the client:

    import java.util.*public class MySecondWebService { 
    ArrayList<Quote> myList;

    public MySecondWebService() {
    Quote tmp;
    myList = new ArrayList<Quote>();
    tmp = new Quote ("Arrr!", "Pirate 1");
    myList.add (tmp);
    tmp = new Quote ("Ye scurvy lubber!", "Pirate 2");
    myList.add (tmp);
    tmp = new Quote ("Argh, it burns! Arr!", "Pirate 3");
    myList.add (tmp);
    }


    public Quote getRandomQuote() {
    int num = (int) (Math.random() * myList.size());
    Quote tmp;
    tmp = myList.get (num);
    return tmp;
    }


    public Quote getRandomQuoteByAuthor (String author) {
    ArrayList<Quote> filtered = new ArrayList<Quote>();
    int num;
    Iterator it;
    Quote tmp;
    it = myList.iterator();
    while (it.hasNext()) {
    tmp = it.next();
    if (tmp.getAuthor() .equals (author)) {
    filtered.add (tmp);
    }

    }

    num = (int) (Math.random() * filtered.size());
    return filtered.get (num);
    }

    }

    The service, as you can see, is pretty simple - but it does show us an extra layer of complexity as far as analysing the wsdl file is concerned. For one thing, we have two operations, one of which takes a parameter. More importantly, both of these methods return a Javabean.

    SOAP allows us to send and receive Javabeans via the SOAP envelope that is sent between a client and a server, but we must first indicate exactly how our Javabean is to be interpreted by both by registering a type mapping. This is a little more complicated than simply placing a jws file into the appropriate directory.

    We've seen how to setup a SOAP client for Javabean goodness, but we need to also configure the service - or more correctly, we need to write a document that tells our service how to map a particular Javabean identifier to a particular class. The document that does that is called a Web Service Deployment Descriptor (WSDD). This is an Apache Axis specific process, and so if you are deploying a web-service on another platform, a different process must be followed.

    The bulk of difficulty in writing a web-service is not in terms of the code - it's a consequence of the deployment model. Generating WSDL and WSDD files is the busywork that must go along with the code, but it's not technically interesting - it's a largely clerical exercise. As such, it is beneficial to make use of tools designed to automate this process as much as possible - those interested in the underlying XML structure of WSDD and WSDL documents are invited to examine the references at the end of this chapter.

    14.4

    Multi-Class Services

    Our service code is very simple - two classes, one of which is a container for some data and the other which creates a list of objects and sends random elements of that list back on request. The only real difference is that our Quote object is a Javabean... not a very interesting one - all it does is hold some data.

    The main difficult comes when compiling this program though Axis - we create the code, we put the main java file into the appropriate directory and change its extension to jws, and then we attempt to access it. We'll get an acknowledgement that there is a web service deployed at the appropriate address, but when we try and obtain the wsdl file we get an error saying that the class Quote cannot be found. Oh no!

    The problem here is that where the web-service is compiled and where your code files are found are scattered around various directories. This is a personal configuration problem that you cannot be told how to solve in this document, but I do have some advice.

    In the Javanomicon (chapter 32) we talk a little about Java packages and how they can be used to make it easier to distribute and organise Java classes. This is a prime example of an area where this is useful. My suggestion is that you begin to maintain a package directory on your hard-drive, and keep it maintained with classes that are to be used in multiple applications or in Java web services. You can add that directory to your classpath, and then compile files with package information.

    You then import the appropriate package in your web services, which makes the code available to you. For the example above, we change our files a little to add some new information at the top:

    package SecondWebService; 
    import java.io.*;


    public class Quote implements Serializable {
    public String author;
    public String quote;

    And:

    package SecondWebService; 
    import java.util.*;


    public class MySecondWebService {
    ArrayList myList;

    This should solve any configuration problems we're likely to encounter. Having setup the class files appropriately, we can now access the wsdl file of our new service which shows that it has been correctly setup. Huzzah!

    Setting this up as belonging to a separate package is especially useful for us here, because our client application is also going to need the Quote.class file for when it deserializes the bean from the envelope. Rather than duplicating the class file, now we only need to import the proper package.

    When we look at our WSDL file, we see the same familiar gobbledygook with an important addition:

    <wsdl:types>
    <schema targetNamespace="http://SecondWebService">
    <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>

    <complexType name="Quote">
    <sequence>
    <element name="author" nillable="true"
    type="xsd:string"/>
    <element name="quote" nillable="true"
    type="xsd:string"/>
    </sequence>
    </complexType>

    </schema>
    </wsdl:types>

    This is the SOAP envelope information regarding our quote Javabean. Javabeans are incorporated into an envelope through the medium of introspection, which we looked at very briefly during the third chapter. Introspection allows a piece of code to programmatically query available methods... the Javabean specification provides a firm context for these methods. If there is a method that begins with get, then it's a property.

    Serialization is the process of turning objects into streams of bytes - what the SOAP process does is use introspection to query all the methods, and then serialize each of the properties ready for inclusion as a tag in the SOAP envelope. This is done automatically by the web service.

    There is one piece of information we must provide, however - we must let the service know how to map a Javabean into an XML element. The actual serialization process is done via introspection, but we need to give the Javabean an appropriate representation in the XML document. This is done via the beanMapping element in an XML document.

    For simple web-services, like the first one we saw in this chapter, Apache Axis will generate a WSDL file for us - this is called a hot deploy. For more complex web services (such as those that involve JavaBeans being transmitted to and from a service), we need to manually provide a WSDD file.

    This is a much simpler document than the WSDL file... traditionally it is called deploy.wsdd. WSDD files are an Apache Axis specific XML file - if you are using another web service framework, you won't need to make use of it. The WSDD file tells Axis how to deploy a particular web-service, and includes some information regarding mapping between XML elements and Java classes (ah-ha!).

    In its simplest format, our WSDD file looks something like this:

    <deployment xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

    <service name="MySecondWebService" provider="java:Axis">
    <parameter name="className"
    value="SecondWebService.MySecondWebService"/>
    <parameter name="allowedMethods"
    value="*"/>
    </service>
    </deployment>

    The key parts here are the service tag... this gives the name of the service that we're setting up. We'll use this in our client to tell it which service it should make use of.

    The first parameter tag (className) allows us to give the fully qualified Java name for the service. This is where the Class file may be found. Since we've set this up within a package called SecondWebService, we pass this as the value of the parameter.

    The second parameter tag (allowedMethods) lists the methods of the class that may be called by a client. If this is a * (which it is here) it means that all methods may be called. You can restrict this to a subset by passing a list of space delimited method names as the value.

    We need to add a little more information in here - this relates to our Javabean, and how we map that into a specific XML element. We make use of a new tag within our wsdd file:

    <deployment xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

    <service name="MySecondWebService" provider="java:RPC">
    <parameter name="className"
    value="SecondWebService.MySecondWebService"/>
    <parameter name="allowedMethods"
    value="*"/>

    <beanMapping xmlns:ns="http://SecondWebService/"
    qname="ns:Quote"
    languageSpecificType="java:SecondWebService.Quote"/>

    </service>
    </deployment>

    The beanMapping element allows our server to work out the kind of XML element name our beam will have. We set it with a namespace of "http://SecondWebService" and with a qname of "ns:Quote". This gives us a fully qualified name for our Javabean (we'll use this in our client).

    The languageSpecificType parameter is what we use to link this tag into a specific Javabean - we pass the fully qualified class name of our Quote object.

    Once we have this file, we need to actually make it active. In Apache Axis, this is done through the use of the AdminClient command which updates the server information with the appropriate type mapping. The framework for serializing the Javabean at the server is now in place.

    However, we need to provide enough information for Java to be able to deserialize the representation back into an appropriate object - an instance of the Javabean. This is the process of deserialization, and we need to provide our client application with an appropriate understanding of how to do this. This links back into our talk of BeanSerializers from the Washing Your Mouth Out With SOAP chapter. We need to make use of the objects we discussed there to build our client application.

    The structure of the code that links to the web service is identical to the client written above, except for some changes to the service and method names. We also need to create a SOAPMappingRegistry ready for setting up the connection between the Bean and the XML representation:

    SOAPMappingRegistry myReg = new SOAPMappingRegistry(); 

    And we need to create an appropriate object for serialization - we have one of these provided for us in the form of the BeanSerializer:

    BeanSerializer myBeanSerializer = new BeanSerializer(); 

    We'll link these two objects together via the mapTypes method in the

    myReg.mapTypes (Constants.NS_URI_SOAP_ENC, new QName ("http://SecondWebService/" 
    , "Quote"), SecondWebService.Quote.class, myBeanSerializer, myBeanSerializer);

    Then we build up our Call object in the usual way, also setting the SOAPMappingRegistry to the appropriate value:

    Call myCall = new Call(); 
    myCall.setSOAPMappingRegistry (myReg);
    myCall.setTargetObjectURI ("urn:MySecondWebServiceService");
    myCall.setMethodName ("getRandomQuote");
    myCall.setEncodingStyleURI (Constants.NS_URI_SOAP_ENC);
    myCall.setParams (params);

    And we get our response object in the same way - we just need to cast it into a Javabean:

    Quote myQuote; 
    returnVal = res.getReturnValue();
    myQuote = (Quote) returnVal.getValue();

    And voila! We have a random quote Javabean transmitted from our quote server. Of course, we're only going to get one of a small selection of quotes... but from little acorns!

    We can also make use of this mapping send objects the other way - there's nothing to stop us adding an addQuote method to our service, and allow individual clients to submit updates which are then available to any client making use of the random request method. Pretty nifty!

    The process of this is important, and underpins why SOAP is such a powerful system:

    SOAP Encoding
    Fig 14.1: SOAP Encoding

    We start off with a Javabean that we wish to transmit to a remote object. The process of introspection can be used to find out what properties there are on the Javabean, giving us a way to query the current state. We then serialize the state of each property into a stream of bytes, and use our type mapping to apply that to an appropriate XML element in the SOAP envelope.

    When the envelope is transmitted, the receiving object turns that stream of bytes into their actual values, and the SOAP mapping tells it what kind of Javabean it should create to store them. It creates a new instance of this (hence the importance of the default constructor method), and then calls the appropriate set method for each property. The end result is a copy of the Javabean that was sent from the first object.

    At the beginning of this book, Javabeans seemed pretty uninteresting in themselves - just a convention for writing particular classes. Hopefully this explains why such conventions are important - with a strict, well-designed format, the whole can be much more than the sum of its parts.

    14.5

    .NET Services

    Almost all of the complexity of deploying web services comes from the framework rather than the code. The big benefit of XML being used as a transmission medium is that it's really easy to make services implementation independent - it doesn't matter if a service is written in .NET and my client is written in Java. Provided they both adhere to the SOAP convention, they can communicate just as easily as if they were both written in the same language. That's a huge benefit.

    However, here we hit a snag between hype and reality - .NET has no support for Javabeans. There are a range of (advanced) Javabean frameworks that allow for this kind of interaction to be abstracted via a range of framework APIs, but we don't have the time to get into those.

    But even just with the simpler types, SOAP offers a huge improvement over the kind of functionality that can be achieved with simple socket programming... although even here, we must be careful and avoid the use of any Java or .NET specific data structures (avoid Hashmaps in Java or Datasets in .NET, for example). The complexType element seen in our WSDL above represents an abstraction of the state of an object away from its implementation. Provided that a complexType uses only cross-platform types and structures, it should be consumable by a .NET client or service.

    Let's look at the code for a simple .NET service. We'll use C# for our implementation language, primarily because it is just Java with a slightly different syntax (see chapters 35 and 36 of the Javanomicon). Our service will inherit from a base class called System.Web.Services.WebService. Other than that, we follow almost exactly the same process as with a Java class, except we mark which methods in the class are going to be web methods... we do this with a tag before the method:

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.Data;
    using System.Diagnostics;
    using System.Web;
    using System.Web.Services;

    namespace HelloWorld
    {

    public class HelloWorld : System.Web.Services.WebService
    {
    public HelloWorld()
    {
    InitializeComponent();
    }

    [WebMethod]
    public string getHelloWorld()
    {
    return "Hello World";
    }
    }
    }

    All .NET components are 'hot deploy'... the WSDL file is generated 'on the fly' when a service is accessed. They can be happily hosted on any suitable IIS installation -there's no need to mess around with Apache or its sometimes arcane internal setup configurations.

    As you can see, the code is very similar to what we've been doing in Java... just a little bit of extra syntax and we're good to go. Setting up a web service in itself isn't very interesting, and neither is setting up a client to access our .NET service... it has no functionality. What would be more interesting is if we could setup a client to access our Java quote server... so let's try that.

    It's ridiculously easy to setup a .NET client using Visual Studio .NET. No, seriously - it's an insult to you. But I'm going to show you anyway, so you can rend your clothes at the thought we started off using Java and SOAP.

    First, we start a new application. We'll draw a Shiny Button on the form. When we press the button, it's going to fire up a message box that shows us a random quote:

    Interface
    Fig 14.2: Interface

    In the Visual Studio IDE, we click on 'Project' and 'add web reference'. It'll bring up the following dialog box:

    Add a Web Reference
    Fig 14.3: Add a Web Reference

    In the URL drop down menu, we enter the http address that contains the WSDL of the service we want to access:

    Navigate to WSDL
    Fig 14.4: Navigate to WSDL

    Then you click 'add reference', which asks you to give it a name. It doesn't matter what it is, but because this one is installed on localhost, that's the name we give it.

    From that point on, we can create a connection to the web service using the same syntax we use to create an instance of an object. The name we give the reference works like a namespace... we can choose a service from there. Our service is called MySecondWebService, so:

    localhost.MySecondWebServiceService myQuotes = new localhost.MySecondWebServiceService 
    ();

    That's it... connection made. Wow!

    We can then call a method on the web-service by using the standard syntax:

    myQuotes.getRandomQuote(); 

    As we know, that method returns an object of type Quote, which is a Javabean... and .NET can't deal with Javabeans. However, the complexType tag in the WSDL gives .NET all the information it needs to create a data structure that holds an instance of all of the simple data types. It doesn't do anything fancy like setting up property definitions, but it does provide suitable public access attributes, meaning we can access the data like so:

    localhost.Quote theQuote = myQuotes.getRandomQuote(); 
    MessageBox.Show (theQuote.quote);

    I know, sickening isn't it? But it's hard to deny that it's exceptionally cool that with a little bit of magic, we can create a connection to a Java based web-service using the .NET framework... and even though we don't get the Javabean itself, the underlying processes of introspection and serialization into an XML document allows for a level of transferability between languages that has been hitherto impossible.

    Obviously if there's any functionality that should go with our Javabean, we're not going to get that in .NET... and if we're using any Java specific data types in our Javabean, then we won't get those either... but for passing a chunk of contextually related data, it's pretty simple.

    14.6

    Conclusion

    So, it may seem pretty mean of me to spend all this time leading you through SOAP in Java, and then pointing out how easy it was to do in .NET. You may even be right - but that's not to say there isn't a lot to be gained by spending time looking at it in its more complex incarnation. Dealing with the SOAP registry and the WSDD file in Axis gave us an important look at how SOAP actually works when encoding Javabeans... the processes of introspection and serialization are very important, and a firm grounding in how they work to incorporate a complex data type into a XML envelope is very valuable.

    We've truly come full circle now - we started off with Javabeans, which seemed like nothing more than a dull convention for writing classes. Through our journey we've seen Javabeans expand into useful components via the use of properties that can inform other objects of changes. We've seen interfaces and polymorphism, and how they underpin good component design. We spent time looking at XML and DOM as a prelude for our first foray into SOAP... we wrote a simple SOAP client and then played about with Servlets (as an opportunity to get used to the Apache Tomcat server software) before coming to SOAP servers (dull) and the interaction of SOAP, XML and Javabeans (more interesting). It's been fun, although you may disagree!

    I've mentioned a number of times through the course of this module that we're into the hard part of programming now - design. We've had to skirt over a number of important points due to the fact it's a single module, but hopefully you've seen some new tricks and tactics that can help you structure your programs in a way that makes them useful as fully fledged components.

    Further Reading

    The following table details further reading on the topic in this chapter, and also any external resources that you may find useful.

    ResourceDescription
    NothingThere's no links as yet, so go find your own you shiftless wasters.

    PreviousTable of ContentsNext

    © 2004-2006 Michael James Heron