WSO2Con 2013 CFP Banner

RESTful Web Services with Apache Axis2

Discuss this article on Stack Overflow
By Kieth Chapman
  • 20 Jun, 2008
  • Level:  Introductory
  • Reads: 38829

In this tutorial, Keith Chapman looks at implementing a REST service in Apache Axis2.

Kieth Chapman

WSO2 Inc.

Introduction

Does Apache Axis2 support REST? This is a question that's been asked over and over on the Axis2 mailing List. While some think that Axis2 supports REST, others argue that it doesn't. What I refer to here as REST support is support for truly RESTful services, not the Axis2 default of http://localhost:8080/axis2/services/<serviceName>/<operationName>. My stand is that that Axis2 supports truly RESTful services. This tutorial will take you through the steps needed to write a truly RESTful service in Axis2.

First, let me give you an introduction to the terms we will be looking at, in this tutorial.

What is REST?

REST, an acronym for Representational State Transfer is a term coined by Roy Fielding in his Ph.D. dissertation[1] to describe an architecture style of networked systems. REST is built upon the correct use of the HTTP verbs and unique URIs that identify a resource. The most common HTTP verbs used are GET, POST, PUT and DELETE and they are often compared to CRUD (create, Read, Update and Delete) operations. The following is an approximate mapping for the above HTTP verbs to CRUD operations.

 

HTTP Verb CRUD Operation
POST CREATE
GET READ
PUT UPDATE
DELETE DELETE

WSDL 2.0 and REST

The WSDL 2.0 HTTP Binding[2] introduces a clean approach to describe REST services in a standard way. It is a fact that some REST fanatics do not like having a contract for RESTful services but here are some of advantages of having a contract:

  • A uniform approach to describing a service
  • Machine processable
  • Defines interactions with a service
  • Ability to develop tools to ease development e.g. Axis2 WSDL2Java tool

Whether ot not REST needs a description language is an ongoing debate[3].

The REST support in Axis2 is powered by the WSDL 2.0 HTTP Binding. The WSDL 2.0 HTTP binding allows users to control the following:

  • Which HTTP operation is used (GET, PUT, POST, DELETE)

  • Input, output and fault serialization - content-type to be used

  • Transfer codings

  • Authentication requirements

  • Cookies

  • HTTP over TLS (https)

RESTful Services in Axis2

In order to write truly RESTful services in Axis2, users need to use WSDL 2.0 deployment (as of the 1.4.1 release of Axis2). Plans are under way to provide the ability to control RESTful properties when POJO (Plain Old Java Object) deployment is used as well. Please refer the section on future directions for more details.

Writing a WSDL 2.0 from scratch could be a daunting task for users who are not too familiar with WSDL. The rest of the tutorial, will take you down an alternative path where we write our code first and then use Axis2 java2wsdl tool to generate a WSDL for us. We will then tinker the generated WSDL to suit our needs. (Alternatively, if you are brave enough, you could author your WSDL 2.0 by hand and then use the wsdl2java tool of axis2 to generate the code).

Simple Example - Student Service

Let's take a simple scenario, where I am required to expose some student details in a RESTful manner. This service will be able to add new students, update existing students details, delete student details and List details of existing students. So this service will essentially have CRUD operations. 

Business  Logic of The Sample Service - StudentService Class

package org.apache.axis2;

import org.apache.axis2.context.MessageContext;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.description.TransportInDescription;
import org.apache.axis2.addressing.EndpointReference;

import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;

public class StudentService {

    private Map<String, Student> map = new HashMap<String, Student>();
    private String baseURL = null;

    public String[] getStudents() {
        int size = map.size();
        String[] students = new String[size];
        Iterator<String> iterator = map.keySet().iterator();
        int i = 0;
        while (iterator.hasNext()) {
            String studentName = iterator.next();
            students[i] = getBaseURL() + "student/" + studentName;
            i++;
        }
        return students;
    }

    public Student getStudent(String name) throws StudentNotFoundException {
        Student student = map.get(name);
        if (student == null) {
            throw new StudentNotFoundException("Details of student " + name + " cannot be found.");
        }
        return student;
    }

    public String addStudent(Student student) throws StudentAlreadyExistsException {
        String name = student.getName();
        if (map.get(name) != null) {
            throw new StudentAlreadyExistsException("Cannot add details of student " + name +
                    ". Details of student " + name + " already exists.");
        }
        map.put(name, student);
        return getBaseURL() + "student/" + name;
    }

    public String updateStudent(Student student) throws StudentNotFoundException {
        String name = student.getName();
        if (map.get(name) == null) {
            throw new StudentNotFoundException("Details of student " + name + " cannot be found.");
        }
        map.put(name, student);
        return getBaseURL() + "student/" + name;
    }

    public void deleteStudent(String name) throws StudentNotFoundException {
        if (map.get(name) == null) {
            throw new StudentNotFoundException("Details of student " + name + " cannot be found.");
        }
        map.remove(name);
    }

    // This method attempts to get the Base URI for this service. This will be used to construct
    // the URIs for the various detsila returned.
    private String getBaseURL() {
        if (baseURL == null) {
            MessageContext messageContext = MessageContext.getCurrentMessageContext();
            AxisConfiguration configuration = messageContext
                    .getConfigurationContext().getAxisConfiguration();
            TransportInDescription inDescription = configuration.getTransportIn("http");
            try {
                EndpointReference[] eprs = inDescription.getReceiver()
                        .getEPRsForService(messageContext.getAxisService().getName(), null);
                baseURL = eprs[0].getAddress();
            } catch (AxisFault axisFault) {                
            }
        }
        return baseURL;
    }
}

This simple service keeps student details in a HashMap with the student name as the key. As student details are kept in a HashMap, we have to make sure that this service is deployed in an application scope. Student details are held in the following student bean, which contains three properties including name ( a String), age (an integer) and subjects (an array of Strings). In order to make things interesting, we will add a couple of custom exception classes as well. The StudentNotFoundException class (thrown when details of a student does not exist) and the StudentAlreadyExistsException class (thrown when duplicate details are attempted).

Student class

package org.apache.axis2;

public class Student {

    private String name;
    private int age;
    private String[] subjects;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String[] getSubjects() {
        return subjects;
    }

    public void setSubjects(String[] subjects) {
        this.subjects = subjects;
    }
}

StudentNotFoundException class

package org.apache.axis2;

public class StudentNotFoundException extends Exception{
    public StudentNotFoundException(String message) {
        super(message);
    }
}

StudentAlreadyExistsException class

package org.apache.axis2;

public class StudentAlreadyExistsException extends Exception {
    public StudentAlreadyExistsException(String message) {
        super(message);
    }
}

Service Descriptor - Services.xml

<serviceGroup>
    <service name="StudentService" scope="application">
        <parameter name="ServiceClass">org.apache.axis2.StudentService</parameter>
        <messageReceivers>
        <messageReceiver mep="http://www.w3.org/ns/wsdl/in-only" class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
        <messageReceiver mep="http://www.w3.org/ns/wsdl/in-out" class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
    </messageReceivers>
    </service>
</serviceGroup> 

The services.xml (service descriptor for a Axis2 Service) simply states the ServiceClass and and the fact that this application is deployed in application scope (remember we keep all the student information in a HashMap, so we want just one instance of the service class to be created). It also states which message receivers will be used for for the two message exchange patterns we will be using. Our application contains four operations, which use an in-out (request-response) message exchange pattern and a single operation that uses a in-only (request only) message exchange pattern.

Try Deploying this Service

Structure of the Service Archieve File

Structure of the Service Archive file

If you were to package this service up in a .aar file (the structure of this archive will be as given above) and deploy it, Axis2 would make all operations available at <serviceName>/<operationName> by default. Which means, you will be able to perform a http GET on http://localhost:8080/axis2/services/StudentService/getStudent?name=keith. Now, this is not truly REST as you can clearly see that you have no control over the URL the operation was exposed under. Also, the only way you can pass in parameters to this service is via query parameters. Is there a way that we can change the URl where this operation is exposed at? Even send parameters into this service in the path segment of the URI? You sure can and that's where WSDL 2.0 comes into play. Let's have a look at how this could be achieved using WSDL 2.0.

In Order To Be RESTful What Should Be My URLs and HTTP Verbs?

The table below lists a brief approximation as to how CRUD operations would map to HTTP verbs. The first step of making the StudentService we've developed RESTful is, to list down HTTP verbs and URLs that these operations will be exposed under. The following table illustrates this:

Operation Name HTTP Verb URL Pattern
addStudent POST /services/studentService/students
updateStudent PUT /services/studentService/student/{name}
deleteStudent DELETE /services/studentService/student/{name}
getStudent GET /services/studentService/student/{name}
getStudents GET /services/studentService/students

You would have noticed that the URLs I have selected contains certain patterns. These URI patterns will be later mapped to WSDL 2.0 properties. Let's get a WSDL 2.0 document gererated for our service so that we can tinker with it and make our service RESTful.

Using java2wsdl tool to generate a WSDL 2.0 document

You can use the java2wsdl commandline utility that ships with Axis2 to generate a WSDL 2.0 document for the StudentService we've written. The java2wsdl command line utility can be found in the bin directory of the Axis2 distribution. For our example scenario we won't be using all posible options that java2wsdl offers. Our usage of java2wsdl is,

sh java2wsdl.sh -wv 2.0 -o <output directory> -of <output file name> -sn <Name of the service> -cp <classpath uri> -cn <fully qualified name of the service class>

The java2wsdl utiliy has more options. You can check all of them out by executing "sh java2wsdl.sh". Here is what you see when the above command is executed:

keith@keith:/opt/axis2-1.4.1/bin$ sh java2wsdl.sh
 Using AXIS2_HOME:   /opt/axis2-1.4.1
 Using JAVA_HOME:       /opt/software/java/jdk1.5.0_06
Usage: java2wsdl [options] -cn <fully qualified class name>

where [options] include:
  -o <output location>                    output directory
  -of <output file name>                  output file name for the WSDL
  -sn <service name>                      service name
  -l <soap address>                       address of the port for the WSDL
  -cp <class path uri>                    list of classpath entries - (urls)
  -tn <target namespace>                  target namespace for service
  -tp <target namespace prefix>           target namespace prefix for service
  -stn <schema target namespace>          target namespace for schema
  -stp <schema target namespace prefix>   target namespace prefix for schema
  -st <binding style>                     style for the WSDL
  -u <binding use>                        use for the WSDL
  -nsg <class name>                       fully qualified name of a class that implements NamespaceGenerator
  -sg <class name>                        fully qualified name of a class that implements SchemaGenerator
  -p2n [<java package>,<namespace] [<java package>,<namespace]...
                                          java package to namespace mapping for argument and return types
  -p2n [all, <namespace>]                 to assign all types to a single namespace
  -efd <qualified/unqualified>            setting for elementFormDefault (defaults to qualified)
  -afd <qualified/unqualified>            setting for attributeFormDefault (defaults to qualified)
  -xc class1 -xc class2...                extra class(es) for which schematype must be generated. 
  -wv <1.1/2.0>                           wsdl version - defaults to 1.1 if not specified
  -dlb                                    generate schemas conforming to doc/lit/bare style

Now, let's get the WSDL 2.0 document generated for our StudentService.

keith@keith:/opt/axis2-1.4.1/bin$ sh java2wsdl.sh -wv 2.0 -o /home/keith/projects/axis2_rest/resources/ -of StudentService.wsdl -sn StudentService -cp /home/keith/projects/axis2_rest/classes/ -cn org.apache.axis2.StudentService
 Using AXIS2_HOME:   /opt/axis2-1.4.1
 Using JAVA_HOME:       /opt/software/java/jdk1.5.0_06
[ERROR] Required MessageReceiver couldn't be found, thus, default MessageReceiver has been used

 Note : You can safely ignore the error it displays.

This will generate a file called StudentService.wsdl in the output directory. The WSDL generated will look similar to this,

Making Our Service RESTful

The WSDL generated will have 3 bindings cause this is the default setting in Axis2. It will have a SOAP 1.1 binding, a SOAP 1.2 binding and a HTTPBinding. We are interested in making our service RESTful, hence, we will concentrate on the HTTPBinding and ignore the rest (we can even take them off the WSDL).

We did decide upon the URL patterns and the HTTP verbs we will be using for are sample service. Let's wire them together now.

The property that allows us to state the HTTP method that an operation will be exposed under is "whttp:method", which is defined at the binding operation level. If this property is not present, it will look for the "whttp:methodDefault" property which can be defined at the binding level. If this too is not present, WSDL 2.0 defaulting rules will be used. We have two operations that we want to expose over GET, hence lets make whttp:methodDefault="GET" and specify whttp:method for the others at the binding operation level.

The property that allows us to state the URL pattern that an operation will be exposed under is "whttp:location", which is defined at the binding operation level. Please refer[4] for more details on what is whttp:location. You may also refer[5] for some tips on deciding what your whttp:location should be.

The following is how we want out HTTP binding to look like:

<wsdl2:binding name="StudentServiceHttpBinding" whttp:methodDefault="GET" interface="tns:ServiceInterface" type="http://www.w3.org/ns/wsdl/http">
    
    <wsdl2:fault ref="tns:StudentAlreadyExistsException"/>
    
    <wsdl2:fault ref="tns:StudentNotFoundException"/>
    
    <wsdl2:operation ref="tns:deleteStudent" whttp:location="student/{name}" whttp:method="DELETE">
        <wsdl2:outfault ref="tns:StudentNotFoundException"/>
    </wsdl2:operation>
    
    <wsdl2:operation ref="tns:updateStudent" whttp:location="student/{name}" whttp:method="PUT">
        <wsdl2:outfault ref="tns:StudentNotFoundException"/>
    </wsdl2:operation>
    
    <wsdl2:operation ref="tns:addStudent" whttp:location="students" whttp:method="POST">
        <wsdl2:outfault ref="tns:StudentAlreadyExistsException"/>
    </wsdl2:operation>
    
    <wsdl2:operation ref="tns:getStudent" whttp:location="student/{name}">
        <wsdl2:outfault ref="tns:StudentNotFoundException"/>
    </wsdl2:operation>
    
    <wsdl2:operation ref="tns:getStudents" whttp:location="students"/>
    
</wsdl2:binding>

A Few More Tweaks to The WSDL

In our example, we are using Axis2's built-in RPCMessageReceivers, and deploying the service using the WSDL. In order for this to work out-of-the-box, there are a few tweaks needed in the WSDL. (This is needed only because we are using a seperate bean class (and a custom Exception classes) in our service. If your service does not use any bean classes this section can be overlooked safely).

In the WSDL generated, Axis2 puts all bean classes into a namespace called " http://axis2.apache.org/xsd", while other elements are left in "http://axis2.apache.org". We need to have all data types in the same namespace to use the RPCMessageReceivers out-of-the-box. Here is how you accomplish this task:

  1. In the WSDL generated, you will notice two schema sections under the types section. The schema section which defines StudentAlreadyExistsException, Student, and StudentNotFoundException is in the namespace http://axis2.apache.org/xsd. Get the three complex types that defines these three data types, and move them to the other schema section (the one which defines elements in the http://axis2.apache.org namespace). Once this is done, you can get rid of the schema that was in the http://axis2.apache.org/xsd namespace. 
  2. Once you do the above alteration, your schema becomes a bit inconsistent (as some elements still refer to stuff in the http://axis2.apache.org/xsd namespace). Let's fix that now. In the description section of the WSDL, there will be a namespace prefix that refers to http://axis2.apache.org/xsd (its xmlns:ax21="http://axis2.apache.org/xsd" in my case). Edit it so that it refers to http://axis2.apache.org (make it xmlns:ax21="http://axis2.apache.org").
  3. Two of the custom exception classes that we moved (in step 1), extend off the Exception class, which is defined in the http://axis2.apache.org namespace . The namespace prefix ax22 which refers to this namespace was defined in the schema section that we god rid of. Now we need to restore that link. This can be done by making dataTypes refer to ax21:Exception (instead of ax22:Exception, We just defined xmlns:ax21="http://axis2.apache.org" in the previous step.)
  4. Get rid of the import statement at the top of the schema section (get rid of  <xs:import namespace="http://axis2.apache.org/xsd"/>).

Now, your edited WSDL should look similar to this.

Deploy your service

Now we are all set. package the service up in the same manner we did previously and deploy your service. There is a bit of an extra step that you need to do this time though. Make sure you drop in the WSDL we edited into the META-INF directory of the service archieve. Verify that your service is up and running by tryng to get its WSDL 2.0 document at http://localhost:8080/axis2/services/StudentService?wsdl2. You can even try to get the list of students at http://localhost:8080/axis2/services/StudentService/students.

Future Directions

  • Currently (As of Axis2 1.4.1) WSDL 2.0 deployment is the only way to author a truly RESTful services. We plan to extend this to POJO deployment as well. This will alow users to control the HTTPBinding properties in the WSDL through annotations of the service class or parameters in the services.xml. Will keep this article updated once that feature is added to Axis2.
  • Consider adding the helper method we used to generate the links (getBaseURL()) as a helper method into Axis2.

Conclusions

Question: Does Axis2 support REST?

Answer: It sure does.

This article has taken you through the process of writing RESTfull services in Axis2. It also illustrates how WSDL 2.0 fits into the picture and demonstrates its ability to describe truly RESTful services. Yes, there are improvements that could be done to make life easy and we do plan to do so in future releases. 

[1] Dr. Roy Fielding's Ph.D. dissertation - http://roy.gbiv.com/pubs/dissertation/top.htm

[2] WSDL 2.0 HTTP Binding - http://www.w3.org/TR/wsdl20-primer/#more-bindings-http

[3] Does REST need a description language - http://www.infoq.com/news/2007/06/rest-description-language

[4] What is whttp:location? - http://wso2.org/library/3715

[5] Things to note when deciding on values to use for whttp:location - http://wso2.org/library/3725

Author

Keith Chapman, Senior Software Engineer, WSO2 Inc. keith at wso2 dot com

AttachmentSize
StudentService-src.zip5.71 KB
StudentService.aar5.58 KB
originalWSDL.xml11.55 KB
StudentService.wsdl10.91 KB