Skip to content

Instantly share code, notes, and snippets.

@danbev
Created October 11, 2012 11:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save danbev/3871798 to your computer and use it in GitHub Desktop.
Save danbev/3871798 to your computer and use it in GitHub Desktop.
RestEasy Integration

RestEasy Integration with AeroGear Controller

This document is intended to describe AeroGear Controllers integration with RestEasy.

Background

Currently, the routes that AeroGear Controller can handle are "one way" in the sense that they go through AeroGear Controller are forwarded to a view, which can be populated by a data model provided by the Controller for the route in question.
For example:

route()
      .from("/customers/{id}")
      .on(RequestMethod.GET)
      .to(TargetClass.class).targetMethod(pathParam("id");

A request for customer/10 would be routed through the targetMethod of TargetClass, and a view for the configured Route would be populated and the request forwarded to the view. For example, a JSP page displaying customer information for user with id 10 might be the view in the above case.

Now, the next step is to allow clients to be able to invoke REST endpoint. The difference here is that the request pattern, instead of forwarding to a view, the data itself would be returned. The are other use cases as well where data might not be returned but there will always be a response, perhaps a status and a location where data has been PUT/POST:ed.

From a users perspective they will configure a REST endpoint in the same, or similar, way as is currently done for "normal" routes. The integration with RestEasy will be done by programmatically configuring the endpoint behind the scene. This document's goal is to figure out how this integration and configuration can be done.

Programmatic endpoint configuration in RestEasy

Please see the RestEasy Programmatic Configuration for details.

Integration point for a JAX-RS implementation in AeroGear Controller

This section will discuss how RestEasy will be hooked into AeroGear Controller.

An application that uses AeroGear Controller is deployed as a web application archive (WAR), with the aerogear-controller.jar in the WEB-INF/lib directory. Upon deploying the WAR, the servlet container will scan the deployment for @WebFilter annotation (among other things). AeroGear Controller contains such a servlet filter named AeroGear. All request to the web application will go through the AeroGear filter. AeroGear gets injected with a Router instance which is able to dispatch to the routes that the end users has configured.

This section will uses RestEasy as an example provider but any JAX-RS implementation could be used with AeroGear Controller. RestEasy can be deployed as a Servlet, or a Servlet Filter. Currently, we are looking into what the best way is to integrate by adding RestEasy 3.0-alpha-1-SNAPSHOT as a dependency of aerogear-controller. The following branch is being used to experiment resteasy-integration.

So, where should a JAX-RS implementation be hooked into AeroGear Controller?
Well, how about a separate Servlet Filter, for example named AeroGearRest, with gets injected with a RestRouter of which RestEasyRouter would be an instance.
RestRoute currently looks like this:

public interface RestRouter extends Router {
    public void init(FilterConfig filterConfig);
    public void registerRoutes();
    public void unregisterRoutes();
}

The init() method gives a router implementation access to the FilterConfig instance if it needs some configuration data to exist in web.xml for example. The registerRoutes() and unregisterRoutes() is where the RestRouter implementation would register the configured routes with its runtime engine. For a concrete example please refer to registerRoutes() and unregisterRoutes().
RestRoutes should be moved to a separate project, if we decide to go with this approach, to allow for different implementations to be used, but also for users that don't require Rest endpoints as they would then avoid the extra dependencies.

This solution assumes that AeroGear, which handles the "normal" Model View Controller (MVC) call, will only contain routes that it can handle. Likewise, AeroGearRest will only contain routes that it can handle. The order of the filters does not matter and one will be run first, its hasRoutes() method will determine if it can service the http request, and if it can't the next filter will get a chance to handle the request. Perhaps we should call what is currently named DefaultRoute as DefaultMvcRoute and move everything that is specific to a package named mvc. The class has not been renamed but as an example of this you'll find that the class has been moved to a the mvc.

Configuration of REST routes in AeroGear Controller

[Work in progress]
What differs between a "normal" route and a REST route?
Most of the properties of a "normal", or an mvc route, are common to Rest routes as well. One difference is that Rest routes can specify what the consume and what they produce. So lets start by using these two properties and build from there. Perhaps a Rest route could look like this

QueryParam Example returning plain text

route()
       .from("/query")
       .consumes("*/*")
       .on(RequestMethod.GET)
       .produces("text/plain")
       .to(HelloRest.class).query(queryParam(String.class, "msg", "no value passed"));
curl http://localhost:8080/aerogear-controller-demo/query?msg="testing"
testing

curl http://localhost:8080/aerogear-controller-demo/query
no value passed

This route simply returns what every is passed to it, or if no query parameter named 'msg' was passed the default value is used.

PathParam Example returning JSON

route()
       .from("/path/{id}")
       .consumes("*/*")
       .on(RequestMethod.GET)
       .produces("application/json")
       .to(HelloRest.class).path(pathParam(String.class, "id"));
curl  http://localhost:8080/aerogear-controller-demo/path/1234
{"msg":"1234"}

HeaderParam Example returning XML

route()
       .from("/header")
       .consumes("*/*")
       .on(RequestMethod.GET)
       .produces("text/xml")
       .to(HelloRest.class).header(headerParam(String.class, "msg", "defaultHeader"));
curl -H 'msg: headerValue'  http://localhost:8080/aerogear-controller-demo/header
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<simpleXml>
    <msg>headerValue</msg>
</simpleXml>

CookieParam Example

route()
       .from("/cookie")
       .consumes("*/*")
       .on(RequestMethod.GET)
       .produces("text/plain")
       .to(HelloRest.class).cookie(cookieParam(String.class, "msg", "defaultCookie"));
curl -b msg=cookieValue  http://localhost:8080/aerogear-controller-demo/cookie
cookieValue

The examples above would be built into a DefaultRestRoute by AbstractRoutingModule's buildRest. You might have noticed that there is a another 'build' method named buildMvc. These build methods would be called by their respective Router implementations, for "normal/mvc" routes that would be DefaultRouter, and for rest DefaultRestRouter. This way, the Routers only hold routes of the type that they can service, which enables us to use different filters for different type as mentioned earlier in this document. Please ignore the state of the code and there place where there is code duplication but this will be taken care of if this approach is decided upon.

Issues

Packaging RestEasy in demo

When deploying aergear-controller-demo to AS7, RestEasy is added as an implicit module, this means that if we need the version of RestEasy that we ship we will need to add an exclude.

This can be checked by inspecting the following classes and their classloader:

JaxRsConfig.class.getClassLoader()
     Module "deployment.aerogear-controller-demo.war:main" from Service Module Loader
ResourceMethodRegistry.class.getClassLoader()
     Module "org.jboss.resteasy.resteasy-jaxrs:main" from local module loader @b23d12 (roots: /path/to/jboss-as-7.1.1.Final/modules)

To add an exclude for AS7 there are two options, either provide a jboss-deployment-structure.xml file in WEB-INF, or add the exclude as a MANIFEST Header. Currently, the demo branch uses a jboss-deployment-structure.xml.

Building RestEasy

I've had an issue building RestEasy using the lastest from upstream. I've posted to the RestEasy dev list to get help on this matter. This branch contains the changes I had to make to things working.

RestEasy release schedule

If we are to contribute the programmatic configuration of endpoints to RestEasy it will not be considered until RestEasy's 3.0 release according to the RestEasy team. According to the RestEasy roadmap there is a 3.0-alpha-1 scheduled for March 2013. This must be taken into consideration as it could possibly effect AeroGear Controllers release date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment