How to build a simple REST server in J2EE.
For this example I tried to avoid any frameworks targeting at a simple Java code.
If you want to skip my explanations, all the code is here: https://github.com/tan-tan-kanarek/java-rest-server-example
1. web.xml configuration
In order to direct all REST paths into a single entry point, I defined a single mapping to /*:
<servlet-mapping> <servlet-name>REST Application</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
And defined my servlet with list of available services in Services init-param:
<servlet> <servlet-name>REST Application</servlet-name> <jsp-file>/index.jsp</jsp-file> <!-- Controllers configuration --> <init-param> <param-name>Services</param-name> <param-value>com.example.services.UsersService</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> </servlet>
2. Single entry point – index.jsp
The entry point reads the servlet configuration (from web.xml) and registers all the configured services:
String services = getInitParameter("Services"); RestServiceManager.setServices(services);
The list of services is cached:
The entry point triggering request-deserializer that returns a request object, the request object must be set with context that defines the DB interface:
RestRequest restRequest = RestRequestDeserializer.deserialize(request); restRequest.setContext(context);
The request is executed and returns response serializer:
IRestResponseSerializer restResponseSerializer = restRequest.execute();
And finally printing out the serialized response:
3. Deserializing the request – RestRequestDeserializer
The request-deserializer is a static class, its most important method is parse that currently just deserialize the JSON, no matter if it arrived as application/json content-type or as json field inside multipart/form-data content-type (which could be useful if you want to upload files and send JSON in the same request).
The output of the serialization process is always a request-object.
Note that the request-deserializer is very simple thanks to the fact that all the routing points are always built of single service and single action, that enables me to implement controllers without defining a unique routing path for each action. I also don’t have to check the HTTP method because the action is part of the URL.
4. Request objects
I defined three types of requests.
Invalid request accepts the exception object in its constructor and just sets the response error, it’s instantiated by the request-deserializer in case of invalid inputs.
Service request accepts controller, action (method) and arguments. The service-request executes the action and set the response object or the response error in case of exception.
The execute method returns the response-serializer that later will serialize the response object and response errors into a well formed JSON or XML.
Multi request handles a series of requests, that can save the HTTP round-trip in case you want to execute several requests together.
Note that this model force me to have flat list of services, it simplifies the routing URLs as well as the server code.
Scheme request Parses the entire API and provides an XML descriptor that describe the entire API scheme, including controllers, actions, objects and enumerators. The scheme is auto-generated based on the code and configured services.
5. Services (controllers)
I created a simple users-service example with CRUD action (add, update, delete, get and search). The users-service is performing DB actions on a simple sqlite example file (run Install.java to create it).
Normally I would have a single service for each data object while each service has CRUD actions for its data object, trying as much as I can to avoid nested objects, for example, if user has devices, I won’t add devices array on user object, I will prefer to create a new service called user-device that manages each user-device separately.
6. Data model
An example user data model implemented in order to add, update, delete and search users.
7. Response serializer
Note that the response already contains errors and warnings in addition to the response content, so you don’t have to choose between response and error, you can return them both, that also means that the HTTP response code is always 200 OK, unless there is a true HTTP error, this way I distinguish between applicative error and HTTP protocol errors.