REST Framework
Warning: This describes REST framework before version exo-ws-2.0
1 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
2 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.
2.1 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.

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:
- Only one parameter of a method can be not annotated. This parameter represents the body of the HTTP request.
- 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.
- 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.
2.2 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".
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.
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 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).
2.4 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.
2.5 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: