Skip to main content

Web Service Programming for the Masses, Part I: Developing the Web Service API

Posted by stoicflame on January 9, 2008 at 11:33 AM PST

So you've got a web application that needs to expose a Web service API. Maybe you're going to be writing a href="http://en.wikipedia.org/wiki/Rich_Internet_application">Rich Internet Application (RIA) using href="http://en.wikipedia.org/wiki/Adobe_Flex">Flex or some
AJAX Framework. Or maybe you'll be developing a desktop application that needs to interface with your online data. Or perhaps you've got a few business
partners that want to integrate with your application.

Whatever the reason, you need a solution that not only fits your client-side requirements, but one that is also easy to develop, easy to debug, easy to
maintain, and easy to enhance. Don't be too hasty with your choice of a Web service implementation technology. href="http://weblogs.java.net/blog/stoicflame/archive/2007/10/why_roi_vs_soi_1.html">REST is not always the best answer. It might
be, but not always. Take some time to research your needs, gather your requirements, and look at other remoting technologies that are available. Then decide
what you need.

This is the first part of a tutorial will walk you through developing a Web service API that could meet the requirements of all of the above-mentioned use cases. For the sake of
clarity and brevity, we'll keep the operations simple, but by the time we're done, we'll have a fully-functional Web service API that exposes a bouquet of
endpoints: SOAP, REST/XML, href="http://www.json.org/">JSON, GWT-RPC, and even href="http://en.wikipedia.org/wiki/Action_Message_Format">AMF (for all you Flex developers). In Part II of this tutorial, we'll prove out the Web service API by building a rich AJAX
application with an embedded Flash component that accesses it.

By the end of this two-part tutorial, we'll have established a good custom-fit Web service development
process for maintaining your Web service API in your favorite development environment.

For those of you who would like to see the example code on your local filesystem, you can check it out of SVN at http://svn.codehaus.org/enunciate/trunk/enunciate/src/samples/addressbook/.

Step 1: Create Your Project

We will be exposing a Web service API for an address book application. We will be using Maven 2 and href="http://enunciate.codehaus.org">Enunciate 1.6 to build and package our
application. (Note that we could also use Ant to build the application, but Maven will get us started more quickly.)

We'll label our application “addressbook” and assign it a group id “net.java.ws.addressbook”. Assuming Maven is already installed, we can use its href="http://maven.apache.org/plugins/maven-archetype-plugin/">archetype
plugin to bootstrap the project from the command line:

mvn archetype:create -DgroupId=net.java.ws.addressbook -DartifactId=addressbook

This will create a simple project for us with an empty Java source file and an associated test. You can open up the project in your favorite IDE;
Maven has plugins available that will create project files for whichever one you like to work with.

Before our project is fully usable, we need to tweak it to declare our dependencies, configure the compile settings, and add any additional build tools. As
for our dependencies, we need only one additional dependency on Enunciate's runtime libraries (groupId: org.codehaus.enunciate, artifactId:enunciate-rt). We
also need to declare a Maven plugin for Enunciate to build the Web service application (with the “assemble” goal). For deployment, we
can use the Maven Jetty plugin to deploy and run the application. Finally, we need to configure
Maven's compiler plugin to compile for JDK 1.5 compatability
and make sure we're using “war” packaging since we're building a web application. In the end, our project descriptor looks like this:

addressbook/pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 " title="http://maven.apache.org/maven-v4_0_0.xsd">
">http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.java.ws.addressbook</groupId>
  <artifactId>addressbook</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>addressbook</name>
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.enunciate</groupId>
        <artifactId>maven-enunciate-plugin</artifactId>
        <version>1.6</version>
        <executions>
          <execution>
            <goals>
              <goal>assemble</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.enunciate</groupId>
      <artifactId>enunciate-rt</artifactId>
      <version>1.6</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Step 2: Create Your Domain

The domain of an address book application is very simple. It consists of a basic Contact object. The Contact has fields for an id, name, phone number, two
address fields, and city. We'll also add a contact type enumeration object ("friend", "family", or "professional") and create an object representing a contact list
for use when we want to retrieve multiple contacts from a Web service request. You'll also note that we've annotated Contact and ContactList with a
JAXB
annotation, @javax.xml.bind.annotation.XmlRootElement, which will allow us to return these types as XML elements from a REST request (see below).

addressbook/src/main/java/net/java/ws/addressbook/domain/Contact.java:
package net.java.ws.addressbook.domain;

import javax.xml.bind.annotation.XmlRootElement;

/**
* A contact in the address book.
*
* @author Ryan Heaton
*/
@XmlRootElement
public class Contact {

  private int id;
  private String name;
  private String phone;
  private String address1;
  private String address2;
  private String city;
  private ContactType contactType;

  /**
   * The id of the contact.
   *
   * @return The id of the contact.
   */
  public int getId() {
    return id;
  }

  /**
   * The id of the contact.
   *
   * @param id The id of the contact.
   */
  public void setId(int id) {
    this.id = id;
  }

  /**
   * The name of the contact.
   *
   * @return The name of the contact.
   */
  public String getName() {
    return name;
  }

  /**
   * The name of the contact.
   *
   * @param name The name of the contact.
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * The phone of the contact.
   *
   * @return The phone of the contact.
   */
  public String getPhone() {
    return phone;
  }

  /**
   * TThe phone of the contact.
   *
   * @param phone The phone of the contact.
   */
  public void setPhone(String phone) {
    this.phone = phone;
  }

  /**
   * The first address field of the contact.
   *
   * @return The first address field of the contact.
   */
  public String getAddress1() {
    return address1;
  }

  /**
   * The first address field of the contact.
   *
   * @param address1 The first address field of the contact.
   */
  public void setAddress1(String address1) {
    this.address1 = address1;
  }

  /**
   * The second address field of the contact.
   *
   * @return The second address field of the contact.
   */
  public String getAddress2() {
    return address2;
  }

  /**
   * The second address field of the contact.
   *
   * @param address2 The second address field of the contact.
   */
  public void setAddress2(String address2) {
    this.address2 = address2;
  }

  /**
   * The city of the contact.
   *
   * @return The city of the contact.
   */
  public String getCity() {
    return city;
  }

  /**
   * The city of the contact.
   *
   * @param city The city of the contact.
   */
  public void setCity(String city) {
    this.city = city;
  }

  /**
   * The contact type.
   *
   * @return The contact type.
   */
  public ContactType getContactType() {
    return contactType;
  }

  /**
   * The contact type.
   *
   * @param contactType The contact type.
   */
  public void setContactType(ContactType contactType) {
    this.contactType = contactType;
  }
}
addressbook/src/main/java/net/java/ws/addressbook/domain/ContactType.java:
package net.java.ws.addressbook.domain;

/**
* A contact type.
*
* @author Ryan Heaton
*/
public enum ContactType {

  friend,

  family,

  professional

}
addressbook/src/main/java/net/java/ws/addressbook/domain/ContactList.java:
package net.java.ws.addressbook.domain;

import javax.xml.bind.annotation.XmlRootElement;
import java.util.Collection;

/**
* A list of contacts.
*
* @author Ryan Heaton
*/
@XmlRootElement
public class ContactList {

  private Collection contacts;

  /**
   * The contact list.
   *
   * @return The contact list.
   */
  public Collection getContacts() {
    return contacts;
  }

  /**
   * The contact list.
   *
   * @param contacts The contact list.
   */
  public void setContacts(Collection contacts) {
    this.contacts = contacts;
  }
}

Step 3: Create Your Services

The next step involves creating the services that will comprise our Web service API. We'll create an AddressBook interface that defines three operations:
getContact, findContactsByName, and findContactsByType, each of them throwing an AddressBookException in case something goes wrong.

Furthermore, we have to identify our interface as a Web service. We do this by applying the JAX-WS annotation @javax.jws.WebService to the interface. As it
turns out, this service interface also lends itself nicely to a mapping to a REST endpoint. We can decide, for example, to map the getContact method to an HTTP
GET that will take the id parameter from the URL in the form of a context parameter. To perform the mapping to a href="http://weblogs.java.net/blog/stoicflame/archive/2007/10/why_roi_vs_soi_1.html">resource-oriented interface, we use
Enunciate's proprietary REST annotations (@RESTEndpoint, @Noun, @Verb, @ProperNoun, etc.)

addressbook/src/main/java/net/java/ws/addressbook/services/AddressBook.java:
package net.java.ws.addressbook.services;

import net.java.ws.addressbook.domain.ContactList;
import net.java.ws.addressbook.domain.Contact;
import net.java.ws.addressbook.domain.ContactType;

import javax.jws.WebService;

import org.codehaus.enunciate.rest.annotations.*;

/**
* An address book that provides access to a set of contacts.
*
* @author Ryan Heaton
*/
@WebService
@RESTEndpoint
public interface AddressBook {

  /**
   * Get a contact by id.
   *
   * @param id The id of the contact.
   * @return The contact.
   * @throws AddressBookException If the contact wasn't found.
   */
  @Noun (
    "contact"
  )
  @Verb (
    VerbType.read
  )
  Contact getContact(@ProperNoun Integer id) throws AddressBookException;

  /**
   * Find contacts by name.
   *
   * @param name The name to search for.
   * @return The contacts that were found.
   */
  ContactList findContactsByName(String name) throws AddressBookException;

  /**
   * Find contats by type.
   *
   * @param type The type of contact.
   * @return The contacts.
   */
  ContactList findContactsByType(ContactType type) throws AddressBookException;
}
addressbook/src/main/java/net/java/ws/addressbook/services/AddressBookException.java:
package net.java.ws.addressbook.services;

/**
* @author Ryan Heaton
*/
public class AddressBookException extends Exception {

  public AddressBookException(String message) {
    super(message);
  }
}

Of course, we have to create an implementation of the AddressBook. This is what will query our database and perform the business logic of our application.
For the purposes of this tutorial, we'll just create a simple in-memory store of contacts.

Our implementation must also reference its Web service interface in order to be identified as the implementation for the Web service. We again use the
JAX-WS-specified @javax.jws.WebService and the Enunciate-specific @RESTEndpoint annotation to accomplish this.

addressbook/src/main/java/net/java/ws/addressbook/impl/AddressBookImpl.java:
package net.java.ws.addressbook.impl;

import net.java.ws.addressbook.services.AddressBook;
import net.java.ws.addressbook.services.AddressBookException;
import net.java.ws.addressbook.domain.Contact;
import net.java.ws.addressbook.domain.ContactList;
import net.java.ws.addressbook.domain.ContactType;

import java.util.*;

import org.codehaus.enunciate.rest.annotations.RESTEndpoint;

import javax.jws.WebService;

/**
* @author Ryan Heaton
*/
@WebService (
  endpointInterface = "net.java.ws.addressbook.services.AddressBook"
)
@RESTEndpoint
public class AddressBookImpl implements AddressBook {

  private static final Map STORE = loadContacts();

  public Contact getContact(Integer id) throws AddressBookException {
    Contact contact = STORE.get(id);
    if (contact == null) {
      throw new AddressBookException("contact not found: " + id);
    }
    return contact;
  }

  public ContactList findContactsByName(String name) throws AddressBookException {
    ArrayList contacts = new ArrayList();
    if (name != null) {
      for (Contact contact : STORE.values()) {
        if (contact.getName().toLowerCase().contains(name.toLowerCase())) {
          contacts.add(contact);
        }
      }
    }
    ContactList list = new ContactList();
    list.setContacts(contacts);
    return list;
  }

  public ContactList findContactsByType(ContactType type) throws AddressBookException {
    ArrayList contacts = new ArrayList();
    for (Contact contact : STORE.values()) {
      if (contact.getContactType().equals(type)) {
        contacts.add(contact);
      }
    }
    ContactList list = new ContactList();
    list.setContacts(contacts);
    return list;
  }

  private static Map loadContacts() {
    final int size = 20;
    Random random = new Random();
    String[] firstNames = new String[]{"Sally", "George", "Harold", "Tammy", "Robert", "Daniel", "Jane", "Mike", "David", "John"};
    String[] lastNames = new String[]{"Beach", "Jobs", "Gates", "Bush", "Clinton", "Gore", "Moore", "Jones", "Adams", "Washington", "Smith"};
    String[] addresses = new String[]{"1 First Street", "2 Second Street", "3 Third Street"};
    String[] cities = new String[]{"Long Beach, CA", "New York, NY", "Orlando, FL", "Honolulu, HI", "Oklahoma City, OK"};
    String[] phoneNumbers = new String[]{"111-1111", "222-2222", "333-3333", "444-4444", "555-5555", "666-6666", "777-7777"};

    HashMap contacts = new HashMap();
    for (int i = 0; i < size; i++) {
      Contact contact = new Contact();
      contact.setId(i);
      contact.setName(firstNames[random.nextInt(firstNames.length)] + " " + lastNames[random.nextInt(lastNames.length)]);
      contact.setAddress1(addresses[random.nextInt(addresses.length)]);
      contact.setCity(cities[random.nextInt(cities.length)]);
      contact.setPhone(phoneNumbers[random.nextInt(phoneNumbers.length)]);
      contact.setContactType(ContactType.values()[random.nextInt(ContactType.values().length)]);
      contacts.put(i, contact);
    }

    return Collections.unmodifiableMap(contacts);
  }
}

Step 4: Enable and Configure GWT and AMF (Flex Data Services) Endpoints.

Enunciate will publish your Web service API as href="http://code.google.com/webtoolkit/documentation/com.google.gwt.doc.DeveloperGuide.RemoteProcedureCalls.html">GWT-RPC endpoints and AMF ( href="http://www.adobe.com/products/flex/dataservices/">Flex Data Services) endpoints if you enable this option in the Enunciate
configuration file. The configuration for the GWT module requires a path to the installed Google Web Toolkit and a name for the client module that will be
generated (see the GWT documentation for details). The configuration for the AMF module requires a path to the installed Flex SDK.

addressbook/enunciate.xml:
<enunciate>
  <modules>
    <gwt disabled="false" gwtHome="/path/to/gwt/home" rpcModuleName="net.java.ws.addressbook.AddressBookGWT"/>
    <amf disabled="false" flexHome="/path/to/flex/sdk/home"/>
  </modules>
</enunciate>

Step 5: Build, Package, and Run the Application

Since we've got everything set up and all our endpoints configured, we can now use Maven to invoke Enunciate, build, package, and even deploy our application
to Jetty by running the command:

mvn jetty:run-war

Assuming everything builds as expected, Jetty should be running your Web service API. Things to note:

  1. You can see the documentation for your Web service API by browsing to http://localhost:8080/addressbook/index.html. You'll notice that Enunciate
    generated full documentation for your Web service API scraped from your JavaDocs. You've got your SOAP endpoints and REST endpoints documented along with
    the documentation for the data types.
  2. Each of the endpoints are up and running and servicing requests. Your browser doesn't know how to format requests for the SOAP endpoints, GWT endpoints,
    or the AMF endpoints, but you can see that the endpoints are at least responding to invalid requests by browsing to
    http://localhost:8080/addressbook/soap/AddressBookService, http://localhost:8080/addressbook/gwt/AddressBookService, and
    http://localhost:8080/addressbook/amf/AddressBookService.
  3. Your browser does, however, know how to format a REST request. You can see the XML for a contact by making a request to
    http://localhost:8080/addressbook/rest/contact/4, for example. Or, to see the same contact in JSON format, browse to
    http://localhost:8080/addressbook/json/contact/4.
  4. Enunciate automatically generates client-side code that can be used to access your SOAP endpoints, GWT endpoints, and AMF endpoints (ActionScript
    classes). By default, the client-side libraries that can access your SOAP endpoints are provided as downloads from your Web service API. To see these
    downloads, navigate to http://localhost:8080/addressbook/downloads.html. You can also configure Enunciate to make the client-side GWT code and
    ActionScript classes available for download from the download page. They are not available as a download by default because they are subject to cross-site
    scripting restrictions and are usually for internal consumption (see Part II).
Related Topics >>

Comments

Yes, I believe this is due to a bug in Enunciate 1.6. It happened randomly, and I think this is fixed with the latest 1.7 snapshot.

I'm getting a build failure. It seems that beneath the enunciate-test directory there is some source that references package net.java.ws.addressbook.amf.services, which does not exist.

I suspect there's a problem with your Maven environment. If you'd like to take this discussion to the enunciate-user list, we can likely figure it out there.

i did, i suppose, extacly what's written here but wen i run mvn jetty:run-war i get this build error war artifact 'spring.war.file' not found in the project