Implementing Web Services

Now that we’ve covered servlets and web applications in detail, we’d like to return to the topic of web services. In the previous chapter, we introduced the concept of a web service as an extension of the basic HTTP web transaction, using XML content for application-to-application communication instead of consumption by a web browser client. In that chapter, we showed how easy it is to invoke an RPC-style web service, by using client-side classes generated from a WSDL description file. In this section, we’ll show the other side of that equation and demonstrate how to implement and deploy a web service.

The world of web services has evolved quickly, as have the APIs, buzzwords, and hype. The appeal of this style of interapplication communication using simple web protocols has, to some extent, been tarnished by the design-by-committee approach of many standards bodies and competitors adding features and layers to the web services concept. The truth is that web services were originally simple and elegant when compared to more elaborate protocols, largely because they did not support all of the same semantics—state management, callbacks, transactions, authentication, and security. As these features are added, the complexity returned. We will not cover all aspects of web services in detail but instead focus on the basic RPC style that is appealing for a wide variety of simple applications.

In Chapter 14, we walked through generating and running the client side of a web service (the weather service). In this chapter, we’ll build and deploy our own web service, a simple one that echoes parameters back to the client: EchoService. We’ll be using the built in JAX-WS APIs tools and services container to run this example, although you could deploy the service to Tomcat as well with some additional configuration and packaging into a WAR file.

Defining the Service

To build our client-side API in Chapter 14, we began by downloading the WSDL description file for the (existing) weather service. The WSDL, again, is an XML file that describes the functions of the service and the types of arguments and return values they use. From this description, the wsimport command was able to generate the client-side classes that we needed to invoke the service remotely from Java.

In creating our own web service, we have (at least) two choices. We could follow an analogous process, writing a WSDL document describing our service and using it to generate the necessary server-side framework. The wsimport class that we used before can be used to generate the necessary, annotated service interface for us and we could implement it with our code. However, there is a much easier way: going code-first.

The wsgen command complements wsimport by adding the capability to read annotated Java classes and generate WSDL and related service classes for us. Even better, if we deploy our class using the built-in JAX-WS endpoint publisher, it will take care of generating all of this for us. This means that to test a simple web service, all we really have to do is write a service class that marks the class and service methods with the correct annotations and invoke the publisher. It really couldn’t get much easier.

Our Echo Service

We’ll create a simple service that echoes a few different kinds of values: an int, a String, and one of our own object types (a data holder object), MyObject. In the next section, we’ll examine the data types and how they are handled in more detail. Here is the code:

package learningjava.service;

import javax.jws.*;
import javax.xml.ws.Endpoint;

@WebService
public class Echo
{
    @WebMethod
    public int echoInt( int value ) { return value; }

    @WebMethod
    public String echoString( String value ) { return value; }

    @WebMethod
    public MyObject echoMyObject( MyObject value ) { return value; }

    public static void main( String[] args )
    {
        Endpoint endpoint = Endpoint.publish( "http://localhost:8080/echo", 
            new Echo() );
    }
}

public class MyObject 
{
    int intValue;
    String stringValue;

    public MyObject() { }

    public MyObject( int i, String s ) {
        this.intValue = i;
        this.stringValue = s;
    }

    public int getIntValue() { return intValue; }
    public void setIntValue( int intValue ) { this.intValue = intValue; }

    public String getStringValue() { 
        return stringValue; 
    }
    public void setStringValue( String stringValue ) { 
        this.stringValue = stringValue;
    }
}

We’ve named our {[QUOTE-REPLACEMENT]}echo” methods individually to differentiate them because WSDL doesn’t really handle overloaded methods. (If we’d had a name collision, JAX-WS would give us a runtime warning and choose one for us.) We’ve placed these into a learningjava.service package because it will be easier to work with the tools that way. This package name will be used in the default namespace and package name for generated client code. We could override the default using the targetNamespace attribute of the WebService annotation (and it would probably be wise to do so in order to keep your interface stable).

To deploy our web service, we use the JAX-WS Endpoint class publish() method. This method takes a URI string that indicates the desired host, port, and service path as well as an instance of our class. Obviously, the only host that will work in this arrangement is our local computer, which can normally be accessed by the name: “localhost.” Here, we ran the service on port 8080 under the path “/echo”.

Using the Service

After running the service, drive your web browser to the service URL to get a test page. If you are running the server on the same machine, the URL should be the same as the URI you passed to the publish() method. However, under some circumstances you may have to substitute “127.0.0.1” for “localhost.”

http://localhost:8080/echo
http://127.0.0.1:8080/echo

You should see a description of the service similar to the one shown in Figure 15-1. This tells you that the service is active and gives you its configuration information. You can click on the WSDL link to view the WSDL description file that was generated for our service. The WSDL URL should be your base service URL with “?wsdl” appended.

We can use the WSDL to generate a client and test our service, just as we did in Chapter 14. In the following command, we’ve specified that the generated classes should go into a separate package, learningjava.client.impl, to avoid confusion between the generated classes and our original. We’ve also used the -keep option to retain the source code instead of just the compiled class files (you may want to look at them). The final argument is the URL for our generated WSDL, which you can copy from the test page as shown previously.

Web services description

Figure 15-1. Web services description

% wsimport -p learningjava.client.impl -keep http://localhost:8080/echo?wsdl

Next, we’ll create a small client that uses these generated classes to test the service:

package learningjava.client;

import learningjava.client.impl.*;

public class EchoClient
{
    public static void main( String [] args ) throws java.rmi.RemoteException
    {
        Echo service = new EchoService().getEchoPort();
        int i = service.echoInt( 42 );
        System.out.println( i );
        String s = service.echoString( "Hello!" );
        System.out.println( s );
        MyObject myObject = new MyObject();
        myObject.setIntValue( 42 );
        myObject.setStringValue( "Foo!" );
        MyObject myObj = service.echoMyObject( myObject );
        System.out.println( myObj.getStringValue() );
    }
}

As you can infer from our code, wsimport has generated an EchoService class that represents our service. Service classes may contain multiple service groups, so in order to get our Echo interface, we ask for the Echo “port” with getEchoPort(). (Port is WSDL terminology for a service interface.)

Run the client, and it should bounce the values between the client and server and display them. And there we are! As we said in the introduction, the actual code required to implement and invoke our service is quite minimal and the fact that Java now bundles a simple web service container with the standard edition makes Java an ideal platform for working with web services.

Data Types

As you might guess, because the data for our service has to be expressed as XML in a standard way, there are some limitations to the type of objects that can be transferred. JAX-WS and WSDL support most of the common Java data types and many standard classes directly. Actually, it would be more appropriate to say that JAXB—the Java XML binding API—supports these Java types, as JAX-WS uses JAXB for this aspect. We’ll talk more about Java XML data binding and XML Schemas in Chapter 24.

JAX-WS and JAXB can also decompose JavaBeans-compliant data classes composed of these standard types so that you can use your own classes, as we saw with the MyObject argument in our Echo service.

Standard types

Table 15-1 summarizes the directly supported types (those types that map directly to W3C Schema types; see Chapter 24 for more on XML mapping of Java types.

Table 15-1. Standard types

Category

Types

Primitives and their wrappers

boolean, Boolean, byte, Byte, short, Short, float, Float, int, Integer, long, Long, double, Double

Class types

java.lang.String, java.math.BigDecimal, java.math.BigInteger, java.util.Calendar, java.util.Date, java.util.UUID, java.net.URI, java.awt.Image (as byte [])

Collections

Array types, List types, Set types

Maps and other complex collection types are not currently supported. To maintain the widest compatability for cross-platform web services, it’s best to stick with objects composed of simple data types and arrays or lists of those types.

Value data objects

As we said, JAX-WS can also work with our own object types, although there are several requirements and a caveat to mention. First, to be able to be marshaled, our objects must contain only fields that are supported data types (or further compositions of those). Next, our objects must follow two JavaBeans design patterns. It must have a public, no-args constructor and, if it contains any nonpublic fields, they must have “getter” and “setter” accessor methods. Chapter 22 provides more details about these issues.

Finally, unlike Java RMI, web services do not support the “behavior” or the real identity of our domain objects from end to end. When a Java client uses our WSDL document to generate implementation classes, they will be getting simple data-holder replicas of the classes we specify. These “value objects” will pass along all of the data content of our objects, but are not related to the originals in any other way. Our server-side implementation will, of course, receive the data in the form of our own “real” domain objects. That is why they need to have available constructors so that the server-side framework can create and populate them for us to consume.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.