Popular Posts

Saturday, January 21, 2012

Truly RESTful Services using Apache Wink and WSO2 Application Server





Apache Wink is a complete implementation of JAX-RS v-1.1 specification. JAX-RS is a java API which supports creating web services in RESTful manner.
WSO2 Application Server is the enterprise-ready, scalable web services and web app hosting platform powered by Apache Axis2 and Apache Tomcat.
In this post, we are going to create a RESTful web service using Apache wink libraries and deploy it in WSO2 Application Server. If we summarize the steps that we are going to do in this example;
1. Create a JDBC data source (mySQL)
2. Create the web service with JAX-RS annotations which makes use of the above database
3. Create a web application (war) with apache wink libraries
4. Deploy the web app in WSO2 Application Server

WSO2 provides application developers with a complete middleware platform in cloud. Therefore, we makes use of WSO2 StratosLive PaaS (Platform as a Service) as our development platform. In other words, we are NOT even going to install mySQL or WSO2 Application server in our machines to try this sample out. We will create the database in cloud and host web application in the cloud.

Without discussing further. lets start our journey.

Step 1

We are going to create a primitive customer registration application which consists of a mySQL database with one table called customer. We need to create the DB schema.
WSO2 Stratos middleware platform in cloud allows us to have our own data storage in cloud within seconds. As I explained in this post, all we have to do is;
- Register a new tenant
- Log into https://stratoslive.wso2.com
- Access DataServices Server
- Create a database and a table

CREATE TABLE CUSTOMER_T(customerID int, customerName varchar(100), customerAge int, customerAddress varchar(200));

Once the database and table is created, note the DB connection URL. In my case, it is,
jdbc:mysql://rss1.stratoslive.wso2.com/wink_superqa_com

Step 2

Now, our application data source is ready for use. As I have explained before, we are going to create a customer registration web service which includes all CRUD operations associated with above DB schema. In other words, we can add, delete, read and update customers using the web service. We will implement our webservice in completely RESTful manner so that we makes use of HTTP verbs to invoke service.
Therefore, lets create the Customer bean first. Open your favorite Java IDE and add Customer class with the following properties and associated getters and setters. The complete class can be found at https://wso2.org/repos/wso2/trunk/commons/qa/qa-artifacts/app-server/rest/jaxrs-sample/src/com/beans/Customer.java

public class Customer {

private int customerID;
private String customerName;
private String customerAddress;
private int customerAge;


Step 3

We will use a separate class to handle all communication with the database which we created above. Lets name the class as Storage.java and implement all CRUD operations.

public class Storage {


Connection connection = null;
Statement statement = null;
ResultSet rs = null;

private Connection getConnection() {
String driverName = "com.mysql.jdbc.Driver";
String conectionURI = "jdbc:mysql://rss1.stratoslive.wso2.com/wink_superqa_com";
String userName = "ck_l96225fe";
String password = "ck";


try {
Class.forName(driverName);
try {
connection = DriverManager.getConnection(conectionURI, userName, password);
} catch (SQLException e) {
e.printStackTrace();
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}

} catch (ClassNotFoundException e) {
e.printStackTrace();
}

return connection;
}

//CREATE operation
public void addCustomer(Customer customer) {


try {
connection = getConnection();
statement = connection.createStatement();

String sqlStatement = "INSERT INTO CUSTOMER_T VALUES (" + customer.getCustomerID()
+ ",'" + customer.getCustomerName() + "', " + customer.getCustomerAge() + ",'" + customer.getCustomerAddress() + "')";
statement.execute(sqlStatement);

} catch (SQLException e) {

} finally {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if (connection != null) {
try {
connection.close();
} catch (SQLException e) {

e.printStackTrace();
}
}
}


}

Similarly, add the other methods to UPDATE, DELETE and READ operations. The complete Storage.java class can be found here.

Step 4

Now, we can implement our web service. We are going to use JAX-RS annotations to make our web service completely RESTful. Download Apache wink distribution from here and add WINK_HOME/dist and WINK_HOME/lib to class path of your IDE.
We will have four methods to demonstrate the basic HTTP methods.

  • addCustomer() method is used to add a new customer to the system. We use a HTTP POST request to send the customer details to the web service method.
  • getCustomerName() is just a HTTP GET operation which reads the CUSTOMER_T table and send back the name of the customer associated with given customer ID.
  • updateCustomer() method updates the customer address of the given customer
  • deleteCustomer() method removes a customer record from the table
In JAX-RS, we can define the root resource with @Path annotation as shown below. So, when you send a HTTP request to /qa, it will be directed to the associated class.

@Path("/qa")

public class CustomerService {}

Now, we need to implement the methods associated with CRUD operations. First lets look at addCustomer method. As we defined the root resource at the class declaration level, we can define subresource methods to handle the common HTTP methods. Here, addCustomer is a subresource method and we annotate it with @POST to direct POST requests which are targeted to '/qa' root resource. @Path annotation at the sub resource level resolves the URL path which is targeted to the method. In otherwords, if the request URL is, /qa/customer and the HTTP method is POST, the request is dispatched to addCustomer() method.

@POST

@Consumes("application/x-www-form-urlencoded")
@Path("/customer")
public void addCustomer(@FormParam("customerid") int customerID, @FormParam("customername") String customerName, @FormParam("customerage") int customerAge, @FormParam("customeraddress") String customerAddress){

Storage storage = new Storage();
Customer customer = new Customer();
customer.setCustomerID(customerID);
customer.setCustomerName(customerName);
customer.setCustomerAge(customerAge);
customer.setCustomerAddress(customerAddress);
storage.addCustomer(customer);


}


Also, make a note of the @Consumes annotation, here we define the Content-Type which must be included in the HTTP POST request. In our example, if a POST request to '/qa/customer' is issued with "application/x-www-form-urlencoded" Content-Type, the addCustomer() method will be invoked. If we send any other content in the POST request, this method will not get invoked.

We also use annotated parameters to pass some additional information with a request. For example we can use query parameters, path parameters etc and process them accordingly. In our example, we use @FormParm parameter to extract parameter values from the form posts.

Similarly, we can implement the other methods in our service implementation class.

@GET

@Path("/customer/{customerid}")
@Produces("text/plain")
public String getCustomerName(@PathParam("customerid") int customerID) {
Storage storage = new Storage();
Customer customer = null;
try {
customer = storage.getCustomerDetails(customerID);
} catch (SQLException e) {
e.printStackTrace();
}
return customer.getCustomerName();
}


@PUT
@Consumes("application/x-www-form-urlencoded")
@Path("/customer")
public void updateCustomer(@FormParam("customername") String customerName, @FormParam("customeraddress") String customerAddress){

Storage storage = new Storage();
storage.updateCustomer(customerName, customerAddress);


}

@DELETE
@Path("/customer/{customerid}")

public void deleteUser(@PathParam("customerid") int customerID) {

Storage storage = new Storage();
storage.deleteCustomer(customerID);

}


Step 5

We have completed the implementation of our web service class with JAX-RS annotations. Apache wink needs us to create a sub-class of javax.ws.rs.core.Application if we deploy deploy our application on non-JAX-RS aware containers. At the time of writing, WSO2 Application Server is not JAX-RS aware hence we need to create this particular subclass. This class basically returns the root resource(s).

public class CustomerResourceApplication extends Application {


@Override
public Set> getClasses() {
Set> classes = new HashSet>();
classes.add(CustomerService.class);

return classes;

}
}


Step 6

Next, we need to create the web.xml file for our web application. In addition to the standard constructs in web.xml, we should define that the Apache wink JAX-RS servlet should be initialized with an instance of the above CustomerResourceApplication.

We also define that the requests begin with '/rest/' will be handled by Apache Wink JAX-RS servlet.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Restful service Test Web Application</display-name>
<servlet>
<servlet-name>CustomerServlet</servlet-name>
<servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.sample.CustomerResourceApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CustomerServlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>


Step 7

We are done with our web application now. Create a war with the above classes as well as apache wink libraries. Make sure to place all jars included in WINK_HOME/dist and WINK_HOME/lib in WEB-INF/lib of the web application. You can use the ant build script given here to do all these stuff.

Step 8

Once the web application (CustomerService.war) is ready, log in to https://appserver.stratoslive.wso2.com with your tenant credentials and upload the web application. (Please read my blog post on Apache Tomcat As a Service if you want to know how WSO2 application Server can be used in web app deployment)

Step 9

Finally, we can invoke each of the operations of our web service in truly RESTful manner using a client application such as curl.

HTTP POST:
curl --data "customerid=1&amp;customername=charitha&amp;customerage=33&amp;customeraddress=piliyandala" -X POST -H "Content-Type: application/x-www-form-urlencoded"http://appserver.stratoslive.wso2.com/t/superqa.com/webapps/CustomerService/rest/qa/customer



HTTP GET:
curl -X GET http://appserver.stratoslive.wso2.com/t/superqa.com/webapps/CustomerService/rest/qa/customer/1



HTTP PUT:
curl --data "customername=charitha&amp;customeraddress=colombo" -X PUT -H "Content-Type: application/x-www-form-urlencoded" http://appserver.stratoslive.wso2.com/t/superqa.com/webapps/CustomerService/rest/qa/customer


HTTP DELETE:
curl -X DELETE http://appserver.stratoslive.wso2.com/t/superqa.com/webapps/CustomerService/rest/qa/customer/1

2 comments:

Asanka said...

Great post, Charitha. Few suggestions about the URIs used. Better to use a different root/authority instead of "qa", organization name will be a better example. Path is a collection in this example so it has to be plural (customers).

Charitha said...

Thanks for the comments Asanka. Will update the sample accordingly.