About

Fathom-REST is an opinionated & injectable scaffolding for the Pippo Micro Webframework.

Pippo is a small framework for writing RESTful web applications. It provides easy-to-use filtering, routing, template engine integration, localization (i18n), Webjars support, and pretty-time datetime rendering.

Fathom-REST supports Pippo 0.8.x.

Installation

Add the Fathom-REST artifact.

<dependency>
    <groupId>com.gitblit.fathom</groupId>
    <artifactId>fathom-rest</artifactId>
    <version>${fathom.version}</version>
</dependency>
<dependency>
    <groupId>com.gitblit.fathom</groupId>
    <artifactId>fathom-rest-test</artifactId>
    <version>${fathom.version}</version>
    <scope>test</scope>
</dependency>

Layout

YourApp
└── src
    ├── main
    │   ├── java
    │   │   ├── conf
    │   │   │   └── Routes.java
    │   │   └── controllers
    │   │       ├── EmployeeController.java
    │   │       └── ItemController.java
    │   └── resources
    │       ├── conf
    │       │   ├── messages.properties
    │       │   ├── messages_en.properties
    │       │   └── messages_ro.properties
    │       ├── public
    │       │   └── css
    │       │       └── custom.css
    │       └── templates
    │           ├── base.ftl
    │           ├── employee.ftl
    │           ├── employees.ftl
    │           ├── index.ftl
    │           └── login.ftl
    └── test
        └── java
            ├── conf
            │   └── RoutesTest.java
            └── controllers
                └── ApiControllerTest.java

Note

This module depends on the value of the application.package setting. If you have specified an application package then your Routes class must be ${package}/conf/Routes.java.

Configuration

The standard Pippo settings are directly configured through your conf/default.conf resource file.

application {
  # The prefix to use for internal cookies generated by Fathom.
  cookie.prefix = "FATHOM"

  # ISO Language Code, optionally followed by a valid ISO Country Code.
  # e.g. application.languages=en,de,es,fr,ru
  languages = [en, de, es, fr, ro, ru]
}

Changing the Basepath

By default, Fathom-REST will serve all requests to /. You may specify a different basepath in your conf/default.conf resource file.

servlets {
  fathom.rest.RestServlet = "/mypath"
}

Controlling Initialization Logging

By default, Fathom-REST will log detailed information about your registered content-type engines and routes. You may control this logging.

rest {
  # List content-type engines in the log
  engines.log = true

  routes {
    # List ordered routes in the log
    log = true

    # Include the controller method or handler name
    logHandlers = true

    # Specify the max length of the logged route.  If any logged route exceeds
    # this limit, all routes will be logged on two lines instead.
    maxLineLength = 120
  }
}

Usage

Almost everything you can read in the official Pippo documentation applies to Fathom-REST.

Differences

Fathom-REST takes a slightly different approach to URL mapping compared to upstream Pippo.

In Pippo, the Application class is the core component and is used to register everything.

In Fathom-REST the Pippo Application is an internal component and not meant to be accessed directly. Filters, Routes, and Controllers are registered through the conf/Routes.java class.

Many of the tricks in Fathom-REST have been moved into upstream Pippo and that process will continue for any feature that makes sense to be upstream.

Routes.java

Route registration in Fathom-REST is almost identical to the process in Pippo Routes.

package conf;

public class Routes extends RoutesModule {

  @Inject
  EmployeeDao employeeDao;

  @Override
  protected void setup() {

    // add a filter for all requests to stamp some diagnostic headers
    ALL("/.*", (ctx) -> {
      ctx.setHeader("app-name", getSettings().getApplicationName());
      ctx.setHeader("app-version", getSettings().getApplicationVersion());
    });

    // add a handler for the root page and report it's usage through Metrics
    GET("/", (ctx) -> {
      int count = employeeDao.getAll().size();
      ctx.text().send("Employee Phonebook\n\nThere are {} employees.", count);
    });

    // add all annotated controllers found in the controller package
    addControllers();

    // add all controllers found in the following packages
    addControllers(V1Controller.class.getPackage(),
                   V2Controller.class.getPackage(),
                   V3Controller.class.getPackage());
  }

}

Naming Routes

You may name your routes. This information is used in the registered routes table displayed at startup and during runtime logging of route dispatching.

GET("/", (ctx) -> ctx.text().send("Hello World!")).named("Home Page");

Metrics Collection

It’s very easy to collect Metrics data about your routes.

GET("/", (ctx) -> ctx.text().send("Hello World!")).meteredAs("HelloWorld!");

Requiring Modes

You may want to register a route only for one or more runtime modes.

ALL("/.*", (ctx) -> {
  ctx.setHeader("app-name", getSettings().getApplicationName());
  ctx.setHeader("app-version", getSettings().getApplicationVersion());
}).modes(Mode.DEV, Mode.TEST);

Content-Type by Suffix

You can optionally declare the response content-type with a URI suffix by using the contentTypeSuffixes or requireContentTypeSuffixes methods.

The suffixes are determined by parsing the content-type declarations of your registered ContentTypeEngines.

// Register a route that optionally respects a content-type suffix
// e.g. /executive/54
//      /executive/54.json
//      /executive/54.xml
//      /executive/54.yaml
GET("/employee/{id: [0-9]+}", (ctx) -> ctx.send(employee))
   .contentTypeSuffixes("json", "xml", "yaml").;

// Register a route that requires a content-type suffix
// e.g. /executive/54.json
//      /executive/54.xml
//      /executive/54.yaml
GET("/executive/{id: [0-9]+}", (ctx) -> ctx.send(employee))
    .requireContentTypeSuffixes("json", "xml", "yaml").;

Note

If you specify your parameter without a regex pattern (e.g. {name}) the value of name will include any suffix unless you require the suffix.

Controllers

Fathom-REST can be used like standard controllers in Pippo but they can also be used in a more declarative fashion.

package controllers;

// To be discoverable, a controller must be annotated with @Path.
@Path("/employees")
@Consumes({Consumes.HTML, Consumes.FORM, Consumes.MULTIPART})
@Produces({Produces.JSON, Produces.XML})
public class MyController extends Controller {

  @Inject
  EmployeeDao employeeDao;

  @GET("/?")
  @Produces(Produces.HTML)
  public void index() {
    List<Employee> list = employeeDao.getAll();
    getResponse().bind("employees", list).render("employees");
  }

  @GET("/all")
  @Return(code=200, onResult=List.class)
  public List<Employee> getAll() {
    List<Employee> list = employeeDao.getAll();
    return list;
  }

  @GET("/{id: [0-9]+}")
  @Return(code=200, onResult=Employee.class)
  public Employee getEmployee(int id) {
    // The method parameter name "id" matches the url parameter name "id"
    Employee employee = employeeDao.get(id);
    if (employee == null) {
      getResponse().notFound().send("Failed to find employee {}!", id);
    } else {
      return employee;
    }
  }

}

Consumes

The Consumes annotation declares the content-types which may be accepted by the controller method. You are not required to specify a Consumes annotation but it may make the intenr of your controller methods more clear.

If a Controller method declares Consumes then these types are enforced. One valuable use-case of Consumes is to clearly indicate which methods accept POSTed forms.

Produces and Negotiation

The Produces annotation declares the content-types which may be generated by the controller method. You are not required to specify a Produces annotation but it may make the intent of your controller methods more clear and may be a requirement for use of other modules like Fathom-REST-Swagger.

If a controller method Produces multiple types, the first specified Content-Type is used as the default. A negotiation process will automatically occur to determine and attempt to use the Accept-Type preferred by the Request. If one of the preferred Accept-Types match one of the Produces types, the matching Accept-Type will be used for Response generation. If none of the Accept-Types match the Produces types, then the default Content-Type as declared by the Produces annotation will be used for the Response.

You may also declare Produces on the controller class and it will apply, by default, to all methods unless the method specifies it’s own Produces annotation.

Content-Type by Suffix

If your controller method can @Produce more than one content-type then you may allow overriding the negotiated content-type by the presence of a request URI suffix.

When @ContentTypeBySuffix is detected, Fathom-REST will automatically append an appropriate regular expression suffix to your method URI for the @Produces content-types.

For example, if our controller method @Produces({"application/json", "application/x-yaml"}) and we specify @ContentTypeBySuffix then a request uri of /fruit/1.yaml would force the Request accept-type to be application/x-yaml. Standard content-type negotiation will proceed with the result that the YAML ContentTypeEngine will marshall the Response object.

@GET("/{id: [0-9]+}")
@Return(code=200, onResult=Employee.class)
@Produces({"application/json","application/x-yaml"})
@ContentTypeBySuffix
public Employee getEmployee(int id) {
}

Transparent Parameter Names

Fathom-REST controllers make use of the -parameters flag of the Java 8 javac compiler. This flag embeds the names of method parameters in the generated .class files. By default Java 8 does not compile with this flag set so you must specify the -parameters compiler argument for your build system and your IDE.

Note

You are not required to use this feature. However, if you choose to skip specifying the -parameters flag then you must annotate each controller method parameter with @Param("name"). Obviously this will make your controller method signatures more verbose and you will also be double-maintaining parameter name mappings so use of the -parameters compiler flag is recommended.

Standard Argument Extractors

Argument Extractors allow you to specify an annotation which instructs Fathom-REST how to provide the value of an argument to your controller method.

Fathom-REST includes the following argument extractors:

Annotation Use-Case
@Bean Extract url parameters, query parameters, and/or form fields to a Java object
@Body Marshall a Request body to a Java type via a Content Type Engine (e.g. JSON, XML)
@Header Extract a Request header to a standard Java type
@Local Extract a temporary value stored locally within the Request/Response Context
@Param Extract an url parameter, query parameter, or form field to a standard Java type
@Int Extract a Request integer parameter with a default value
@Long Extract a Request long parameter with a default value
@Float Extract a Request float parameter with a default value
@Bool Extract a Request boolean parameter with a default value

Argument Validation

Fathom-REST supports implied and explicit argument validation.

  1. Implicit validation by regular expression pattern matching of the Route path parameters
  2. Implicit @Required validation for @Body parameters
  3. Explicit @Required validation for all other argument types
  4. Explicit @Min, @Max, & @Range validation for numeric argument types
@POST("/{id: [0-9]+}")
public void renameEmployee(@Min(1) @Max(10) int id, @Required String name) {
}

Ordering

If you are using annotated controller discovery with @Path annotations then you are at the mercy of the JVM for registration order of your controller methods.

If you need deterministic controller method registration order you may specify the @Order(n) annotation on some or all controller methods. Lower numbered methods are registered first.

@GET("/{id: [0-9]+}")
@Return(code=200, onResult=Employee.class)
@Order(5)
public Employee getEmployee(int id) {
}

Naming Controller Routes

You may name your controller routes. This information is used in the registered routes table displayed at startup and during runtime logging of route dispatching. It may also be used by other modules like Fathom-REST-Swagger.

@GET("/{id: [0-9]+}")
@Return(code=200, onResult=Employee.class)
@Named("Get employee by id")
public Employee getEmployee(int id) {
}

Metrics Collection

It’s very easy to collect Metrics data about your controllers. Simply annotate the methods you want to instrument.

@GET("/{id: [0-9]+}")
@Return(code=200, onResult=Employee.class)
@Metered
public Employee getEmployee(int id) {
}

NoCache

You can indicate that a controller response should not be cached by specifying the @NoCache annotation.

@GET("/{id: [0-9]+}")
@Return(code=200, onResult=Employee.class)
@NoCache
public Employee getEmployee(int id) {
}

Declaring Responses Results

You may use the @Return annotation to briefly declare method-specific responses.

This has a few benefits.

  1. it allows you to implement the logic of your controller without directly relying on the request/response/context objects.
  2. it makes the intended results of your controller method execution clear
  3. it facilitates api documentation generators or other static analysis tools
  4. it allows you to send localized messages on error responses based on the Accept-Language of the request
  5. it allows you to declare returned headers, validate them, and optionally inject default values
@GET("/{id}")
@Return(code=200, description="Employee retrieved", onResult=Employee.class)
@Return(code=400, description="Invalid id specified", onResult=ValidationException.class)
@Return(code=404, description="Employee not found", descriptionKey="error.employeeNotFound")
@Return(code=404, description="Employee not found", descriptionKey="error.employeeNotFound")
public Employee getEmployee(@Min(1) @Max(5) int id) {
  Employee employee = employeeDao.get(id);
  return employee;
}

@POST("/login")
@Return(code=200, description="Login successful", headers={ApiKeyHeader.class})
public void login(@Form String username, @Form @Password String password) {  
  Account account = securityManager.check(username, password);
  getContext().setHeader(ApiKeyHeader.NAME, account.getToken());
}

Returning Objects from Controller Methods

If your controller method declares a non-void return type you must declare a @Return annotation for this response.

@GET("/{id: [0-9]+}")
@Return(code=200, onResult=Employee.class)
public Employee getEmployee(int id) {
  return anEmployee
}

Requiring Settings (Controller)

Your controller class may need one or more settings to function and you may specify them as annotated requirements.

Each required setting must be present in the runtime profile configuration and must have a non-empty value otherwise the controller class will not be registered.

@Path("/secret")
@RequireSetting("allow.secret")
public SecretController extends Controller {
}

Requiring Settings (Method)

Your controller method may need one or more settings to function and you may specify them as annotated requirements.

Each required setting must be present in the runtime profile configuration and must have a non-empty value otherwise the controller method will not be registered.

@GET("/secret")
@Produces(Produces.HTML)
@RequireSetting("allow.secret")
public void secret() {
  getResponse().render("secret_page");
}

Requiring Modes (Controller)

You might only want to register a controller class in a particular runtime mode. This is easily accomplished by using one or more of the mode-specific annotations: @DEV, @TEST, and @PROD.

@Path("/debug")
@DEV @TEST
public DebugController extends Controller {
}

Requiring Modes (Method)

You might only want to register a controller method in a particular runtime mode. This is easily accomplished by using one or more of the mode-specific annotations: @DEV, @TEST, and @PROD.

@GET("/debug")
@Produces(Produces.HTML)
@DEV @TEST
public void debug() {
  getResponse().render("debug_page");
}

Template Engines

All standard Pippo template engines are supported.

Engine Artifact
Freemarker ro.pippo:pippo-freemarker
Jade ro.pippo:pippo-jade
Pebble ro.pippo:pippo-pebble
Trimou ro.pippo:pippo-trimou
Groovy ro.pippo:pippo-groovy
Velocity ro.pippo:pippo-velocity

Content-Type Engines

All standard Pippo content-type engines are supported.

Content-Type Engine Artifact
XML JAXB ro.pippo:pippo-jaxb
XML XStream ro.pippo:pippo-xstream
JSON GSON ro.pippo:pippo-gson
JSON FastJSON ro.pippo:pippo-fastjson
JSON Jackson ro.pippo:pippo-jackson
YAML SnakeYAML ro.pippo:pippo-snakeyaml
CSV CommonsCSV ro.pippo:pippo-csv