RESTful web services

Bitbucket source

Components

  • org.amdatu.web.resourcehandler
  • org.amdatu.web.rest.doc
  • org.amdatu.web.rest.doc.swagger
  • org.amdatu.web.rest.doc.swagger.ui
  • org.amdatu.web.jaxrs
  • org.amdatu.web.wink

Dependencies

  • org.amdatu.web.dispatcher or Felix HTTP whiteboard service
  • HTTP Service (e.g. Jetty)
  • jackson-core-asl
  • jackson-mapper-asl
  • jackson-jaxrs

Introduction

Amdatu offers the JAX-RS API to easily implement RESTful web services. JAX-RS is a Java EE standard which heavily relies on the use of annotations. Adding a RESTful web service in Amdatu is as simple as registering an annotated class as an OSGi service. This will automatically be picked up by the framework. The following example demonstrates how to register a simple service. The examples uses Felix Dependency Manager to register the component, but of course any framework would work.

@Path("demo")
public class DemoResource {
	
 @GET
 @Produces("text/plain")
 /**
  * Will be registered at /demo
  */
 public String hello()  {
  return "hello world";
 }
}
public class Activator extends DependencyActivatorBase{

 @Override
 public void init(BundleContext context, DependencyManager manager) throws Exception {
  manager.add(createComponent().setInterface(Object.class.getName(), null)
         .setImplementation(DemoResource.class));
}

 @Override
 public void destroy(BundleContext context, DependencyManager manager) throws Exception {
 }
}

Self documenting REST endpoints with Swagger

Amdatu ships with a bundled version of Swagger to allow all REST endpoints to document themselves. All you really have to do is deploy those Swagger bundles and (by default) point your browser at http://localhost:8080/ui/index.html to see all endpoints. You can drill down into any of them, inspect all their methods, parameters and even invoke them directly from your browser.

To add extra information, you can use the provided @Description(“This is a description.”) annotation everywhere where it makes sense: methods, parameters, etc. They will be picked up and shown in Swagger automatically.

@Path annotation and HTTP methods

The @Path annotation is used to specify on which URL the resource will be available. It is required on the class level, and can optionally also be used on methods. See the following code as an example.

@Path("demo")
public class DemoResource {
	
 @GET
 @Produces("text/plain")
 /**
 	* If no @Path is specified on the method the endpoint is registered on the application's @Path (/demo in this case).
 	* Only one method of the same HTTP type (GET/POST/PUT etc.) can be registered on the same @Path. 
 	*/
 public String hello() {
  return "hello world";
 }
 
 
 @POST
 @Consumes("application/json")
 /**
  * For each HTTP method a method can be registered on the same @Path
  */
 public void saveHello(String body) {
  //Do something awesome
 }

 @GET
 @Produces("text/plain")
 @Path("hello")
 /**
	* A @Path method on a method will register the endpoint on a sub path of the @Path of the class.
	* This example will be registered to /demo/hello
	*/
 public String hello()  {
  return "hello moon";
 }
}

Path and Query parameters

In JAX-RS it’s easy to retrieve information from the path, headers and query parameters using annotations on method parameters.

@GET
@Produces("application/json")	
public ConferenceList list(@QueryParam("filter") String filter) throws Exception {
 System.out.println(filter);
 return null;
}
@GET
@Produces("application/json")	
@Path("{id}")
public Conference getById(@PathParam("id") long id) throws Exception {
 System.out.println(id);
 return null;
}

Content types

In most cases it is most convenient to use JSON as content type for RESTful web services. Most languages and devices can easily work with JSON. XML is of course an alternative. You can either use JAX-B to serialize/deserialize Java objects from/to JSON and XML, or you can use your own serialization framework. The @Consumes and @Produces annotations should be placed on top of methods to define which content types are supported.

Using Jackson:

@Produces("application/json")
public String list() throws Exception {
 ObjectMapper objectMapper = new ObjectMapper();
 StringWriter sw = new StringWriter();
 objectMapper.writeValue(sw, agendaService.listConferences());

 return sw.toString();
}

@PUT
@Consumes("application/json")
public void save(String body) throws Exception {
 ObjectMapper objectMapper = new ObjectMapper();
 Conference conference = objectMapper.readValue(body, Conference.class);
 System.out.println(conference);
}

Using JAXB:

@GET
@Produces("application/json")
public ConferenceList list() throws Exception {
 return ConferenceList.fromConferences(agendaService.listConferences());
}

@PUT
@Consumes("application/json")
 public void save(Conference conference) throws Exception {
  System.out.println(conference);
}

JAX-RS in a modular world

Deciding how to structure your code into modules is of course very application specific and it’s impossible to give come up with a blue print that works best for every application. A model that works well as a starting point is the following:

  • Each JAX-RS endpoint is deployed in a separate bundle
  • Don’t write any re-usable code in JAX-RS endpoints

Let’s say we have an endpoint /speakers and an endpoint /conferences. Both endpoints can be deployed in separate bundles for maximum re-usability. The code related to storing and retrieving the data from a datastore should be deployed in separate bundles and exposed as OSGi services. This way we could re-use the Java API without deploying the RESTful web services. Take the following picture as an example:

RESTful Components

Providing static resources

Another bundle in the Amdatu Web project allows you to easily serve static resources from a bundle. All you need to do is to add two simple manifest headers to a bundle:

X-Web-Resource-Version: 1.0
X-Web-Resource: path/to/resources

The resource handler bundle implements the extender pattern and will pick up these manifest headers and register them at the supplied alias. Optionally, you can also specify a context ID, which is used to link these resources to a specific HttpContext (for example, to add authentication).