Thursday, December 23, 2010

Writing JAX-RS REST API Server with Jersey / GlassFish v3

Writing REST API Web Service application in Java EE 6 is now trivial with JAX-RS API and Jersey (the reference implementation of JAX-RS, bundled with GlassFish v3). I'll show you how, with raw real code. ;-)

Requirements

  1. Eclipse IDE, pick the Java EE edition. Latest version is 3.6 SR1 (Helios)
  2. JBoss Tools plugin for Eclipse IDE (probably optional)
  3. A Java EE 6 Server like GlassFish v3 or JBoss AS 6 (not GA yet)

Create the Web (WAR) Project


Create a new Dynamic Web project in Eclipse.

You'll need to choose a Java EE container runtime, it will download the libraries ("Java EE 6 SDK") as well.

Implement the Resource Class


Create a new JAX-RS resource class. What makes it a resource class is you sprinkle it with javax.ws.rs.Path annotation.

I use JBoss Tools and GlassFish server plugin for Eclipse so either or both of them adds the Java EE libraries to my build path, including the most important  here is jsr311-api.jar. If you use Maven you can depend on javax.ws.rs:jsr311-api:1.1.1 artifact.
A sample resource class is like below. Note that I split my resource class to interface and implementation class.

package com.abispulsa.bisnis.service;

import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;

import org.eclipse.emf.ecore.EObject;

/**
* <!-- begin-user-doc -->
* A representation of the model object '<em><b>Refiller</b></em>'.
* <!-- end-user-doc -->
*
* <!-- begin-model-doc -->
* Refill the target mobile number by some amount.
*
* This is the public facing REST API.
* <!-- end-model-doc -->
*
* <p>
* The following features are supported:
* <ul>
*   <li>{@link com.abispulsa.bisnis.service.Refiller#getSession <em>Session</em>}</li>
* </ul>
* </p>
*
* @see com.abispulsa.bisnis.service.ServicePackage#getRefiller()
* @model
* @generated
*/
public interface Refiller extends EObject {

/**
* <!-- begin-user-doc -->
* This actually only queues it. The real work is done in the queue processor job.
* <!-- end-user-doc -->
* @model
* @generated
*/
@Path("refill")
@POST
String refill(@FormParam("mobileNumber") String mobileNumber, @FormParam("voucherCode") String voucherCode);

} // Refiller

You may notice I'm using the mighty EMF to design the resource class. Yes, that's right! ;-)
(ok, so that's the excuse for me for posting this article to EclipseDriven, I'm hoping people will be curious "what's EMF? sounds cool!" hahahaha)

Ok so here's the actual resource class :

package com.abispulsa.bisnis.service.impl;

import javax.ws.rs.Path;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.ecore.impl.EObjectImpl;

import com.abispulsa.bisnis.service.Refiller;
import com.abispulsa.bisnis.service.ServicePackage;
import com.abispulsa.bisnis.service.Session;

/**
* <!-- begin-user-doc -->
* An implementation of the model object '<em><b>Refiller</b></em>'.
* <!-- end-user-doc -->
* <p>
* The following features are implemented:
* <ul>
*   <li>{@link com.abispulsa.bisnis.service.impl.RefillerImpl#getSession <em>Session</em>}</li>
* </ul>
* </p>
*
* @generated
*/
@Path("/")
public class RefillerImpl extends EObjectImpl implements Refiller {

/**
* <!-- begin-user-doc -->
* Make it public constructor for used in JAX-RS.
* We should wrap it inside application and use CDI though.
* <!-- end-user-doc -->
* @generated NOT
*/
public RefillerImpl() {
super();
}


/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated NOT
*/
public String refill(String mobileNumber, String voucherCode) {
return String.format("MobileNumber=%s voucherCode=%s", mobileNumber, voucherCode);
...
}

private void checkAuthentication() {
// TODO Auto-generated method stub

}

...

} //RefillerImpl

There are several things to notice here:

  1. The implementation resource class is @Path annotated. Of course, since you can't instantiate an interface!
  2. It's possible to "split" JAX-RS annotations between interface, class, interface methods and class method. In my example, the interface defines the the REST API and specific structure of the service. However, the implementation can decide where to put the service itself using @Path. The implementation do not override any of the JAX-RS annotations (REST API structure) defined by the interface.
  3. A public constructor is needed (EMF generated default constructor is protected, there is a reason why), because Jersey will instantiate the resource class directly. It's possible to implement javax.ws.rs.Application so you can instantiate your resource classes yourself and configure Jersey for that. More on this later.

Configuring web.xml for Jersey


The last thing is you need to configure the Jersey servlet in your web.xml :

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

<servlet>
<servlet-name>ServletAdaptor</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.abispulsa.bisnis.service.impl</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>ServletAdaptor</servlet-name>
<url-pattern>/api/v1/*</url-pattern>
</servlet-mapping>

<session-config>
<session-timeout>30</session-timeout>
</session-config>

</web-app>

The web.xml above tells the Java EE 6 Container (i.e. GlassFish v3) to:

  1. Create a Jersey servlet at URL path: /api/v1
  2. Load JAX-RS resource classes from my package:
com.abispulsa.bisnis.service.impl com.sun.jersey.config.property.packages init-param contains a comma-separated list of package names (so you can list multiple packages there). Note that this is optional. I use it because my Web/WAR project doesn't actually contain these packages, but the packages are provided by another JAR that I included in WEB-INF/lib. If your resource classes are inside the WAR itself (i.e. built to WEB-INF/classes) you can leave out that configuration and Jersey will find your classes just fine.

Deploy the Web Project


Deploying the app into GlassFish v3 outputs the following log:

INFO: Scanning for root resource and provider classes in the packages:
  com.abispulsa.bisnis.service.impl
INFO: Root resource classes found:
  class com.abispulsa.bisnis.service.impl.RefillerImpl
INFO: No provider classes found.

INFO: GlobalStatsProvider registered
INFO: Initiating Jersey application, version 'Jersey: 1.1.5 01/20/2010 04:04 PM'
INFO: Adding the following classes declared in META-INF/services/jersey-server-components to the resource configuration:
  class com.sun.jersey.multipart.impl.FormDataMultiPartDispatchProvider
  class com.sun.jersey.multipart.impl.MultiPartConfigProvider
  class com.sun.jersey.multipart.impl.MultiPartReader
  class com.sun.jersey.multipart.impl.MultiPartWriter

INFO: Loading application com.abispulsa.bisnis.server.rest at /apbrest
INFO: Instantiated an instance of org.hibernate.validator.engine.resolver.JPATraversableResolver.

INFO: com.abispulsa.bisnis.server.rest was successfully deployed in 8.179 milliseconds.

Testing the JAX-RS REST API Server with curl

Ok so now l want to test it, using curl is my favorite (in Ubuntu/Debian you can just sudo apt-get install curl), but you can use any tool that understands HTTP requests.

ceefour@annafi:~/project/AbisPulsa/workspace/com.abispulsa.bisnis.service$ curl -v --data-urlencode mobileNumber=08123456 --data-urlencode voucherCode=A5 'http://localhost:8080/apbrest/api/v1/refill'

* About to connect() to localhost port 8080 (#0)
*   Trying ::1... connected
* Connected to localhost (::1) port 8080 (#0)
> POST /apbrest/api/v1/refill HTTP/1.1
> User-Agent: curl/7.21.0 (i686-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18
> Host: localhost:8080
> Accept: */*
> Content-Length: 36
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 200 OK
< X-Powered-By: Servlet/3.0
< Server: GlassFish Server Open Source Edition 3.0.1
< Content-Type: text/plain
< Transfer-Encoding: chunked
< Date: Thu, 23 Dec 2010 09:34:31 GMT
<
* Connection #0 to host localhost left intact
* Closing connection #0
MobileNumber=08123456 voucherCode=A5

Alright! So I guess my application works! ;-)

Pretty straightforward, huh?

Using your Own javax.ws.rs.core.Application Implementation


As mentioned before, Jersey will create your resource classes.

If you want to control instantiation of your classes, you can create class that implements javax.ws.rs.core.Application.

After that you configure it in your web.xml like this:

<web-app> <servlet> <servlet-name>Jersey Web Application</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <param-value>MyApplication</param-value> </init-param> </servlet>     ....
I haven't tried it yet, but I think one probable use case is if you want to use Dependency Injection (CDI) in your resource classes.......... ALTHOUGH GlassFish already does this for you (you can use JAX-RS and CDI at the same time), so you probably only use Application for this purpose if the container doesn't support integration between these specs.

Say Goodbye to web.xml


Another, more valid use case and I guess more common if you want to be "pure Java EE 6" is to leave out web.xml altogether:
JAX-RS 1.1 offers a @ApplicationPath annotation applicable to javax.ws.rs.core.Application which let you specify the webcontext and remove the need for any web.xml

References

No comments:

Post a Comment