The REST framework

Requirements

The purpose of eXo REST framework is to make eXo services (i.e. the components deployed inside eXo Container) simple and transparently accessible via HTTP in RESTful manner. In other words those services should be viewed as a set of REST Resources - endpoints of the HTTP request-response chain, we call those services ResourceContainers.

image: Simplified working model

Taking into account HTTP/REST constraints, it is considered that Resources (naturally mapped as Java methods) contained in ResourceContainer conform with the following conditions:

  • they are uniquely identifiable in the same way as it is specified for HTTP i.e. using URI
  • they can accept data from an HTTP request using all possible mechanisms, i.e. as a part of an URL, as URI parameters, as HTTP headers and body
  • they can return data to an HTTP response using all possible mechanisms, i.e. as HTTP status, headers and body
From the implementation point of view:

  • the framework should be as much JSR-311 compatible as possible for the time the Specification is in draft and fully compatible after the standard's release
  • the ResourceContainer components should be deployable and accessible in the same way as an ordinary service

Implementation

In our REST architecture implementation, an HTTPServlet is the front-end for the REST engine. In this RESTful framework, endpoints for HTTP request are represented by java classes (ResourceContainers). All ResourceContainers are run as components of an ExoContainer, so they are configured within the same configuration file.

ResourceBinder and ResourceDispatcher are two other important components of the REST engine. ResourceBinder deals with binding and unbinding ResourceContainer. ResourceDispatcher dispatches REST requests to ResourceContainer. ResourceBinder and ResourceDispatcher are also components of the ExoContainer.

ResourceContainer

A ResourceContainer is an annotated java class. Annotations must at least describe URLs and HTTP methods to be served by this container. But they may also describe other parameters, like produced content type or transformers. Transformers are special Java classes, used to serialize and deserialize Java objects.

A very simple ResourceContainer may look like this:

@HTTPMethod("GET")
@URITemplate("/test1/{param}/test2/")
public Response method1(@URIParam("param") String param) {
//...
//...
  Response resp = Response.Builder.ok().build();
  return resp;
}

The ResourceContainer described above is very simple, it can serve the GET HTTP method for the relative URL /test1/{param}/test2/ where {param} can be any string value. Additionally, {param} can be used as a method parameter of the container by annotating it as : @URIParam("param") String param. For example, in URL /test1/myID/test2 the method parameter "param" has the value "myID".

@URITemplate may be used for annotating classes or methods. If the TYPE scope annotation is for example @URITemplate("/testservices/") and a METHOD scope annotation is @URITemplate("/service1/") then that method can be called with the path /testservices/service1/. All ResourceContainer methods return Response objects. A Response includes HTTP status, an entity (the requested resource), HTTP headers and OutputEntityTransformer.

rest1.png

A client sends a request, after some operations, the HTTP request is parsed into three parts: the HTTP request stream, which consists of the body of the request, HTTP headers and query parameters. Then ResourceDispatcher gets this request. During the start of ExoContainers ResourceBinder collects all available ResourceContainers and after the start passes the list of ResourceContainers to the ResourceDispatcher. When ResourceDispatcher gets the request from a client it tries to search for a valid ResourceContainer. For this search ResourceDispatcher uses @HTTPMethod, @URITemplate and @ProducedMimeType annotations. The last one is not always necessary, if it is not specified this is set in / (all mime types). When ResourceDispatcher found the "right" ResourceContainer it tries to call a method with some parameters. ResourceDispatcher will send only parameters which ResourceContainer requests. In the code example above the parameter is a part of the requested URL between "/test1/" and "/test2/". ResourceContainer methods can consist only of some well-defined parameters. See the next rules:

  1. Only one parameter of a method can be not annotated. This parameter represents the body of the HTTP request.
  2. All other parameters should be of java.lang.String types or must have a constructor with the java.lang.String parameter and must have annotation.
  3. If the parameter which represents the HTTP request body is present, then in the annotation @InputTransformer a java class must be defined that can build the requested parameter from java.io.InputStream. About transformation see below. After request processing ResourceContainer creates Request. Request is a special Java object which consists of HTTP status, a response header, an entity, a transformer. The entity is a representation of the requested resource. If Response has an entity it also must have OutputEntityTransformer. OutputEntityTransformer can be set in a few different ways.

Response

Response is created by Builder. Builder is an inner static Java class within Response. Builder has some preset ways to build Response, for example (see code example above). The method ok() creates Builder with status 200 (OK HTTP status) and ok() returns again a Builder object. In this way a developer can again call other Builder methods. For example:

//...
Document doc = ....
Response response = Response.Builder.ok().entity(doc, "text/xml").transformer(new XMLOutputTransformer()).build();
In the code example above Response has HTTP status 200, an XML document like entity, a Content-Type header "text/xml" and OutputEntityTransformer of type XMLOutputTransformer. The method build() should be called at the end of process. In the same way a developer can build some other prepared Responses, like CREATED, NO CONTENT, FORBIDDEN, INTERNAL ERROR and other. In this example we set OutputEntityTransformer directly. Another mechanism to do this is the same like in the case with InputEntityTransformer.
//...
@HTTPMethod("GET")
@URITemplate("/test/resource1/")
@InputTransformer(XMLInputTransformer.class)
@OutputTransformer(XMLOutputTransformer.class)
public Response method1(Document inputDoc) {
//...
  Document doc = ....
  Response response = Response.Builder.ok(doc, "text/xml").build();
  return response;
}
InputEntityTransformer is described in the annotation to the method "method1". OutputEntityTransformer is described in the annotation to the method "method1".

Transformer

All transformers can be created by EntityTransformerFactory. Transformers can be divided in two groups. Transformers from the first one extend the abstract class InputEntityTransformer. InputEntityTransformer implements the interface GenericInputEntityTransformer, and GenericInputEntityTransformer extends the interface GenericEntityTransformer. This architecture gives the possibility to use one class EntityTransformerFactory for creating both types of transformers (input and output). At first we will speak about InputEntityTransformer.

InputEntityTransformer

All classes which extend the abstract class InputEntitytransformer must override the method ObjectreadFrom(InputStream entityDataStream). This method must return an object with the same type as ResourceContainer requires in method parameters (one not annotated parameter). This mechanism works in the following way. For example, a class which extends InputEntityTransformer will be called SimpleInputTransformer. So, SimpleInputTransformer must have a simple constructor (without any parameters). SimpleInputTransformer also has two methods void setType(Class entityType) and Class getType(). Those methods are described in InputEntityTransformer. And, as we said above, SimpleInputTransformer must override the abstract method Object readFrom(InputStream entityDataStream). So, a developer needs to create a class which can build a Java Object from InputStream and then create annotation to ResourceContainer or some method in ResourceContainer. This annotation must define the type of InputEntityTransformer. Here is the code of the annotation InputTransformer.

//...
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface InputTransformer {
  Class<? extends InputEntityTransformer> value();
}
Then ResourceDispatcher gets InputTransformer from the factory during runtime then builds an Object from stream and adds it to the parameter array. See the code below.
//...
  InputEntityTransformer transformer = (InputEntityTransformer) factory_.newTransformer(resource.getInputTransformerType());
  transformer.setType(methodParameters[i]);
  params[i] = transformer.readFrom(request.getEntityStream());
//...
  Response response = (Response) resource.getServer().invoke(resource.getResourceContainer(), params);
A developer can find some prepared transformers in the package "org.exoplatform.services.rest.transformer".

OutputEntityTransformer

OutputEntityTransformer can be used to serialize the Object which represents the requested resource to OutputStream. OutputEntityTransformer, in future we will call it SimpleOutputTransformer, can be defined in a few ways.

Now some more words about transformation. The RESTful framework has two multi-purpose input/output transformers. One of them is JAXB(Input/Output)Transformer, and another one is Serializable(Input/Output)Transformer. The first one uses JAXB API for serializing and deserializing an object. Serializable (Input/Output)Transformer uses the own methods of an entity for serialization and deserialization. Here is an example:

//...
public class SimpleSerializableEntity implements SerializableEntity {

  String data;

  public SimpleSerializableEntity() {
  }
	
  public void readObject(InputStream in) throws IOException {
    StringBuffer sb = new StringBuffer();
    int rd = -1;
    while((rd = in.read()) != -1)
      sb.append((char)rd);
    data = sb.toString();
  }

    public void writeObject(OutputStream out) throws IOException {
    out.write(data.getBytes());
  }
}
SerializableInputTransformer will try to use the method void readObject(InputStream in) and SerializableOutputTransformer will try to use the method void writeObject(OutputStream in).

Binding and unbinding components (ResourceContainers)

ResourceBinder takes care of binding and unbinding components which represent ResourceContainer. All ResourceContainers must be defined as ExoContainer components in configuration files, for example:

<component>
  <type>org.exoplatform.services.rest.samples.RESTSampleService</type>
</component>
ResourceBinder is an ExoContainer component and implements the interface org.picocontainer.Startable (see the picocontainer documentation). So at the start of ExoContainer ResourceBinder collects all available ResourceContainers. ResourceBinder makes validation for all ResourceContainers. At least each ResourceContainer must have the @HTTPMethod annotation and @URITemplate annotation. Other annotations are not obligatory. It's not allowed to have few ResourceContainers or methods with the same @HTTPMethod and @URITemplate annotation value. For example, if one container/method has the following code:
public class SimpleResourceContainer implements ResourceContainer {
  @HTTPMethod("GET")
  @URITemplate("/services/{id}/service1/")
  @InputTransformer(StringInputTransformer.class)
  @OutputTransformer(StringOutputTransformer.class)
  public Response method1(String data, @URIParam("id") String param) {
  // ... 
  }
}
than another component with @HTTPMethod("GET") and @URITemplate("/services/service1/{id}/") can't be bound. And now we'll try to explain this situation. On the one hand URITemplate is defined by value /services/{id}/service1/, instead of {id} there can be any other string value, so the combination /services/service1/service1/ is possible and valid. On the other hand another URITemplate /services/service1/{id}/ can have the string service1 instead of {id}, so for this resource the combination /services/service1/service1/ is possible and valid. And now we have two resources with the same URITemplate and the same HTTPMethod. This situation is not obligatory, we can't say what method we must call! In this case ResourceBinder generates InvalidResourceDescriptorException. Binder also checks the method parameters. In parameters only one parameter without annotation and of free type is allowed. All other parameters must have String type (or have a constructor with String) and be annotated. In other cases InvalidResourceDescriptorException* is generated. If all components have the "right" @HTTPMethod and @URITemplate annotations they should be bound without any problems. ResourceDispather gets a collection of ResourceContainers during the start and everything is ready to serve a request.

Note: within the scope of one component (one class) validation for URI templates is not implemented, so a developer must take care of @URITemplate and @HTTPMethod. Both of them must not be the same for different methods. Except if @QueryTemplate or/and @ProducedMimeTypes annotation is used.

ResourceDispatcher

Now let's talk about the features of ResourceDispatcher. ResourceDispatcher is the main part of the RESTful engine. Above we said some words about transformation and the role of ResourceDispatcher in this process. Now we are ready to talk about annotated method parameters. In one of the code examples above you could see the next construction

public Response method1(String data, @URIParam("id") String param) {
The method method1 is described with two parameters. The first parameter String data is built from the entity body (InputStream). The second one - String param is annotated by a special type Annotation. This Annotation has the following code:
@Target(value={PARAMETER})
@Retention(RUNTIME)
public @interface URIParam {
  String value();
}
How does it work? After having found the right method ResourceDispatcher gets the list of method parameters and starts handling them. Dispatcher tries to find the not annotated parameter (this entity body) and addresses InputEntityTransformer in order to build the required Object from InputStream. When dispatcher finds an annotated parameter it checks the type of annotation. It is possible to have four types of annotation: @URIParam, @HeaderParam, @QueryParam and @ContextParameter. If dispatcher has found the annotation @URIParam("id") then it takes the parameter with the key "id" from ResourceDescription and adds it into the parameter array. The same situation is with header, context and query parameters. So within the method a developer gets only the necessary parameters from header, query and uri. Some more information about @ContextParameters. @ContextParameters can be set in the configuration file of a component:
<component>
<type>org.exoplatform.services.rest.ResourceDispatcher</type>
  <init-params>
   <properties-param>
     <name>context-params</name>
     <property name="test" value="test_1"/>
   </properties-param>
  </init-params>
</component>
In this case any service can use this parameter, for example:
...method(@ContextParam("test") String p) {
  System.out.println(p);
}

After its execution you should see "test_1" in console.

And in each service the next context parameters can be used. The name of these parameters are described in ResourceDispatcher:

public static final String CONTEXT_PARAM_HOST = "host";
public static final String CONTEXT_PARAM_BASE_URI = "baseURI";
public static final String CONTEXT_PARAM_REL_URI = "relURI";
public static final String CONTEXT_PARAM_ABSLOCATION = "absLocation";

After that dispatcher finishes collecting parameters that the method requires, invokes this method and passes the result to the client as it is described above.

This part of code can explain how this mechanism works:

//...
      for (int i = 0; i < methodParametersAnnotations.length; i++) {
        if (methodParametersAnnotations[i] == null) {
          InputEntityTransformer transformer = (InputEntityTransformer) factory
              .newTransformer(resource.getInputTransformerType());
          transformer.setType(methodParameters[i]);
          params[i] = transformer.readFrom(request.getEntityStream());
        } else {
          Constructor<?> constructor = methodParameters[i].getConstructor(String.class);
          String constructorParam = null;
          Annotation a = methodParametersAnnotations[i];
          if (a.annotationType().isAssignableFrom(URIParam.class)) {
            URIParam u = (URIParam) a;
            constructorParam = request.getResourceIdentifier().getParameters().get(u.value());
          } else if (a.annotationType().isAssignableFrom(HeaderParam.class)) {
            HeaderParam h = (HeaderParam) a;
            constructorParam = request.getHeaderParams().get(h.value());
          } else if (a.annotationType().isAssignableFrom(QueryParam.class)) {
            QueryParam q = (QueryParam) a;
            constructorParam = request.getQueryParams().get(q.value());
          } else if (a.annotationType().isAssignableFrom(ContextParam.class)) {
            ContextParam c = (ContextParam) a;
            constructorParam = contextHolder_.get().get(c.value());
          }
          if (methodParameters[i].isAssignableFrom(String.class)) {
            params[i] = constructorParam;
          } else {
            params[i] = (constructorParam != null) ? constructor.newInstance(constructorParam) : null;
          }
        }
      }
      Response resp = (Response) resource.getServer().invoke(resource.getResourceContainer(), params);
//...

The more detailed schema looks like this: rest2.png


Creator: Andrey Parfonov on 2007/07/09 09:49
Copyright (c) 2000-2009. Allright reserved - eXo platform SAS
1.6.13286