Controllers
Controllers process requests for dynamic resources and orchestrate generation of an appropriate response.
Controllers publish "action" methods which act as entry-points for RESTful request processing.
You can inject request context into parameters of action methods. Controller invocation follows
a defined lifecycle. Ways to use Dependency Injection with controllers.
Introduction
Controllers process requests for dynamic resources.
The
Resources chapter showed how Controller classes are mapped to dynamic resources.
Controllers are named after the C part in the MVC (Model-View-Controller) pattern.
Their job is to accept a resource request and to orchestrate request processing.
When a controller has finished, a response should have been generated. This usually means:
- Evaluate request parameters (normal parameters, path params, matrix-params, cookies, headers, request content)
- Call back-end services to fulfill the request, passing request parameters in appropriate form.
The result produced by those servies is the model (the M in MVC). Civilian does not make any assumption about the form
of your services and models.
- Turn the model into a view (the V in MVC), suitable to be sent as response.
This might be a JSON or XML representation of the model, or a HTML page which displays model data, etc.
Civilians content serialization and
template system can help to implement this.
- Alternative responses would be to send an error status,
or a redirect to another resource.
Controllers are not meant to contain a lot of code. Ideally they are just the glue between the frontend request and back-end services,
translating request parameters, invoke back-end services and convert data models into response views.
What makes a controller a controller
A class in your application is recognized and treated as controller class if
- it directly or indirectly extends the Controller base class,
- its class name ends with
Controller
, e.g. SearchController
or PrintController
,
- it is placed in a package below the applications root controller package.
(As explained in the resources chapter, we scan the classpath to find all controller classes
and build the resource tree. 2. and 3. help to do this efficiently. There are several
ways to alter these requirements).
Controller action methods
For RESTful request processing, the controller should take request properties such as
into account and create a specific response for the request.
Civilian helps to implement such a case-by-case analysis of requests with the concept of controller action methods:
Action methods are methods in a Controller class which act as entry points for RESTful processing. They use
annotations to specify the requests types for which they can be used.
Given a request, the most suitable action method is selected and invoked.
Request |
Dispatched to |
GET /apps/crm/users/789 HTTP/1.1 Accept: application/json
|
@Get @Produces("application/json") com.crm.users.id.IndexController#getJson()
|
POST /apps/crm/users/789 HTTP/1.1 Accept: text/html Content-Type: application/x-www-form-urlencoded
|
@Post @Produces("text/html") com.crm.users.id.IndexController#editForm()
|
What makes a method to an action method? The method must
- be public and not static
- have a
void
return value
- either have one or more HTTP method annotations (see below)
- or must override an inherited action method from a parent controller class
You can choose any method name you want. (Of course you may also define as many "normal" methods in a Controller class as you want).
The following describes action method annotations in detail:
@Get, @Post, etc: HTTP method annotations
consist of these annotations:
@Get,
@Post,
@Put
@Delete,
@Head,
@Options
or
@RequestMethod.
A controller method needs at least one of these annotations to qualify as action method.
public class MyController extends Controller
{
@Get @Post @RequestMethod("TRACE") public void run()
{
...
These method annotations signal that only requests with such HTTP method(s) can be processed by the action method.
In the example above the run-method only processes GET, POST or TRACE requests.
@Produces annotation
Action methods can use the
@Produces annotation to specify that they only can process
requests with certain
Accept-Headers.
The client uses this header to specify
the
acceptable content types for the response body. Since different response types usually require
different processing, the @Produces annotation allows to
- overall specify which content types can be produced by the controller
- and which action method should be invoked for a specific request
@Get @Produces("text/html") public void asHtml() {
... // produces a HTML response
}
@Get @Produces("application/json;q=2, application/xml") public void asData() {
... // produces a JSON or XML response
}
@Get @Produces("image/*") public void asImage() {
... // produces a image
}
The @Produces annotations specifies one or more content types. Wildcards are allowed, as are quality parameters q
with values ≥ 0
to express server-side preferences of certain content types.
The client accept-header will also contain a list of (wildcarded, parameterized) content-types.
The content negotiation algorithm is used to match client preferences with
server capabilities to determine the actual content-type of the response.
An action method without a @Produces annotation makes the bold statement that it can handle all preferred content-types of the client.
@Consumes annotation
Action methods can use the
@Consumes annotation to specify that they only can process
requests with payloads of a certain
content-type.
@Put @Consume("application/json") public void processJson() {
... // handle requests with a JSON body
}
@Put @Consume("image/png", "image/gif") public void processImages() {
... // handle requests with a image body
}
Action method selection
When a controller receives a request, it must select the most appropriate action method to process the request,
or signal an error if it has no suitable method.
Selection starts with all action methods declared by the controller class
and its superclasses. Methods declared in derived classes are given higher priority.
- Test: Only candidate methods pass whose HTTP method annotation(s) includes the request HTTP method.
If there are no more candidates, selection stops with a 405 method not allowed error.
- Test: Only candidate methods pass which can consume the content-type of the request.
The method
either does not have a @Consumes annotation or the annotation value contains the request content-type.
If there are no more candidates, selection stops with a 415 unsupported media type error.
- Test: Only candidate methods pass which can produce one of the acceptable response content types.
An algorithm implemented by the ContentNegotiation class gives a score to each potential content type.
The best scoring content type and the providing action method is selected as best match.
If there is no match, selection stops with a 406 not acceptable error.
Action method parameters
Controller action methods can declare Java parameters. When the method is invoked, the Civilian runtime will inject argument values into those parameters.
You must restrict to certain parameter types and/or use parameter annotations to specify what you want to inject.
(In the following
parameter is used both for
Java method parameters and
request parameters, so don't get confused).
Inject request parameters
All kinds of request parameters can be injected. The method parameter must be annotated with
Example? Example!
@Get public void youNameIt(
@Parameter("name") String name,
@Parameter("when") @LocaleValue Date when,
@PathParam("customerId") Integer custId,
@MatrixParam("p") @DefaultValue("0") int page,
@HeaderParam("x-eval") List<String> evalHeaders,
@CookieParam("prefs") Cookie prefs,
@CookieParam("optout") boolean optout)
{ ...
The annotation value specifies the name of the request parameter (which may or may not be the same as the method parameter name).
If a path parameter is injected, the type of the method parameter must equal the type of
the
PathParam.
For all other parameters the injected argument is the value of the request
parameter converted to the type of the method parameter. These method parameter types are supported:
String
(of course since request parameters are strings)
- any class for which a Type implementation exists in the applications type library.
The Type implementation will parse the string value and construct the target object value.
The default type-library includes Types for all primitive Java types and its class counterparts, and common Date classes.
- any class with a constructor which takes a single string argument.
- any class with a public static method named
valueOf
or fromString
which takes a single String parameter
and returns an object of that class.
java.util.List
, java.util.Set
, java.util.SortedSet
. Many request parameters can have multiple
values, therefore these collection parameters will receive all values, not just the first one.
If no generic type argument is given on the collection, the collection will contain string values. Else the generic type argument
must be a supported simple type and the values will be converted accordingly.
- Value
- @CookieParam can also inject the whole Cookie object and not just the cookie value.
Conversion of parameter string values to another type is by default locale independent. But especially for parameters
locale dependent parsing can be what you want. In this case add the
@LocaleValue annotation.
The @DefaultValue annotation can be used to specify a default value which is injected if
the request does not contain the parameter.
Inject request content
You can annotate a method parameter with @RequestContent to inject the request content:
@Put @Consumes("application/json") public void placeOrder(@RequestContent Order order)
{ ...
Conversion of the request content depends on the type of the method parameter: If the type of the method parameter is
java.io.InputStream
an InputStream for the request content is injected
java.io.Reader
or java.io.BufferedReader
a Reader
for the request content is injected
byte[]
the raw request content is injected
String
the decoded request content is injected
- for any other type Civilians content serialization mechanism is used
to produce an parameter instance from the request content. In this case you want to use a @Consumes annotation
to restrict to content-types which can be handled by your application.
Inject response content
Action methods which return data (e.g. as XML or JSON) can use Civilians content serialization
to easily marshall the data:
@Get @Produces("application/json") public void getOrder()
{
Order order = new Order();
... // fill order object with data
getResponse().write(order); // serializes to JSON
}
For a slightly more compact syntax you can use the
@ResponseContent annotation to achieve
the same effect. A new instance is injected into the parameter. When the action method ended and the response is not committed, the
object will be written to the response:
@Get @Produces("application/json") public void getOrder(@ResponseContent Order order)
{
... // fill order object with data
}
An alternative to the @ResponseContent annotation is to derive your data class from the
ResponseContent class.
@Get @Produces("application/json") public void getOrder(Order /*extends ResponseContent*/ order)
{
... // fill order object with data
}
Inject request or response objects
When a controller action method is invoked the request and response are available via getRequest()
and getResponse(). Similar you can access the application,
the context and other context variables.
But if you prefer you can also inject request and response objects via method parameters (no annotations needed):
@Get public void process(Request request, Response response)
{ ...
Inject multiple request parameters as Java bean
The signature of a controller action method can become crowded if it contains a lot of parameters.
This especially happens if you want to process a posted HTML form with many form controls.
The @BeanParam annotation allows you to inject a bean object into
a controller method parameter, with the bean properties populated by request values.
These properties are supported:
- Bean setter methods. If not otherwise annotated, the request parameter with the name
of the bean property will be injected into the property.
- Other public methods, with a single argument and an annotation what to inject.
- Public fields. If not otherwise annotated, the request parameter with the name
of the field will be injected into the field.
@Post public void process(@BeanParam Data data)
{ ... }
public class Data
{
/*implicit: @Parameter("name")*/ public void setName(String s) { ... }
/*implicit: @Parameter("length")*/ public int length;
@MatrixParam("page") public void initPage(int n) { ... }
@PathParam("customerId") public Integer customeId;
@BeanParam public void setMoreData(MoreData md);
}
Build own annotations to inject custom objects
Civilian allows to build own annotations for action method parameters. Please study
the Inject sample how to do this.
import org.myapp.annotations.Order;
@Post public void process(@Order Order order)
{ ...
Injection errors
Converting request parameters or content to the method parameter type may fail due syntactically invalid data.
In this case the action method is not invoked, but instead a BadRequestException is thrown which will
by default result in a 400 bad request error sent to the client.
You can avoid injection error by using a Value parameter. If an conversion error occurs the Value object
will store the exception, else will contain the parsed value:
@Get public void process(@Parameter("page") Value<Integer> page)
{
if (!value.hasError())
{
Controller Lifecycle
The lifecycle of a controller is simple: A controller instance is created
to process a single request, and then discarded.
This has the following important consequences:
- A controller class itself must not be thread-safe.
- A controller class can define own properties to store temporary data. The controller base class actually defines several
properties, e.g. the request property.
Controller invocation
The entry point for request processing are action methods. But the call to the negotiated action method is embedded in calls to
special init- and exit-methods.
The exact sequence of method calls when a controller
processes a request is as follows:
-
try
- checkAccess(). Should test if access to the resource is allowed.
if (!Response.isCommitted())
- Perform content negotiation to determine the action method.
if (action method found)
else
- reject(). Sends a 405, 406 or 415 error.
-
catch
-
finally
- exit(): Place cleanup code in this method.
Controller architecture
Controller classes all derive from the
Controller base class. Its also a good idea to introduce own
controller base classes in your application, to let derived classes inherit common functionality.
Controller base classes can especially provide meaningful implementations of
checkAccess(),
init(),
setCaching(),
onError(e) and
exit().
For example a base controller class might implement checkAccess() to test if the user is
logged in and if not redirect to a login page or send a 403 forbidden response. All derived controller would automatically inherit
that access control. The Security chapter discusses this topic in detail.
Another example is demonstrated by the JPA / Resource Local sample. A base controller
class uses exit() to commit or rollback a JPA transaction.
Dependency injection
If you intend to use
Dependency Injection (DI) in your web application,
then Controller objects are the ideal targets to inject dependencies.
Controller objects are created per request. You can intercept Controller creation by providing a
ControllerFactory
during
application startup.
Civilian itself provides factory implementations for Guice and
CDI in order to use Guice or CDI dependency injection on controllers.
Several samples demonstrate this feature.