Routes

Java library for routing HTTP requests.

License

License

GroupId

GroupId

org.baswell
ArtifactId

ArtifactId

routes
Last Version

Last Version

1.3
Release Date

Release Date

Type

Type

jar
Description

Description

Routes
Java library for routing HTTP requests.
Project URL

Project URL

https://github.com/baswerc/routes
Source Code Management

Source Code Management

https://github.com/baswerc/routes.git

Download routes

How to add to project

<!-- https://jarcasting.com/artifacts/org.baswell/routes/ -->
<dependency>
    <groupId>org.baswell</groupId>
    <artifactId>routes</artifactId>
    <version>1.3</version>
</dependency>
// https://jarcasting.com/artifacts/org.baswell/routes/
implementation 'org.baswell:routes:1.3'
// https://jarcasting.com/artifacts/org.baswell/routes/
implementation ("org.baswell:routes:1.3")
'org.baswell:routes:jar:1.3'
<dependency org="org.baswell" name="routes" rev="1.3">
  <artifact name="routes" type="jar" />
</dependency>
@Grapes(
@Grab(group='org.baswell', module='routes', version='1.3')
)
libraryDependencies += "org.baswell" % "routes" % "1.3"
[org.baswell/routes "1.3"]

Dependencies

provided (5)

Group / Artifact Type Version
com.fasterxml.jackson.core : jackson-databind jar [2,)
com.google.code.gson : gson jar [2,)
org.jdom : jdom2 jar [2,)
javax.servlet : servlet-api jar 2.4
com.googlecode.json-simple : json-simple jar [1,)

test (1)

Group / Artifact Type Version
junit : junit jar [4.0,)

Project Modules

There are no modules declared in this project.

Routes

Routes is a Java library for mapping HTTP requests to Java object methods. Routes runs within a Java servlet container and is an alternative to processing HTTP requests with the Servlet API. Routes makes it easy to turn this:

public class ApiRoutes
{
  public String getUsers(HttpServletRequest request)
  {
    ...
    request.setAttribute("users", users);
    return "users.jsp";
  }
}

into an object that accepts HTTP GET requests at the path /api/users and renders the loaded users with the JSP file /WEB-INF/jsps/users.jsp.

Getting Started

Direct Download

You can download routes-1.3.jar directly and place in your project.

Using Maven

Add the following dependency into your Maven project:

<dependency>
    <groupId>org.baswell</groupId>
    <artifactId>routes</artifactId>
    <version>1.3</version>
</dependency>

Dependencies

Routes runs within a Java Servlet container at API 2.4 or greater. Routes has no other external dependencies.

Servlet Container Configuration

There are three different ways Routes can be used within a Servlet container.

Routes Servlet

The RoutesServlet can be used to map HTTP requests to routes. Any HTTP request the RoutesServlet does not find a matching route for is returned a 404 (HttpServletResponse.setStatus(404)).

<servlet>
    <servlet-name>RoutesServlet</servlet-name>
    <servlet-class>org.baswell.routes.RoutesServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>RoutesServlet</servlet-name>
    <url-pattern>/routes/*</url-pattern>
</servlet-mapping>

Routes Filter

The RoutesFilter may work better when Routes is not the only means of serving content for your application.

<filter>
    <filter-name>RoutesFilter</filter-name>
    <filter-class>org.baswell.routes.RoutesFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>RoutesFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

This filter should be placed last in your filter chain because chain processing will end here when a route match is found (chain.doFilter is not called). If no match is found, then chain.doFilter will be called so further processing can occur. This allows, for example, your application to serve up file resources (ex. html, jsp) directly as long as none of your routes match the file resource URL.

In addition to the filter-mapping configuration, you can control which HTTP requests are candidates for routes with the ONLY and EXCEPT filter parameters (this can improve performance when it's known that certain HTTP paths won't map to routes).

<init-param>
   <param-name>ONLY</param-name>
   <param-value>/api/.*,/data/.*</param-value>
</init-param>

This parameter is a comma delimited list of Java regular expressions. In this example all HTTP requests with URL paths that start with /api/ or /data/ will be candidates for routes (as in url-pattern the context path should be left off the expression).

<init-param>
   <param-name>EXCEPT</param-name>
   <param-value>.*\.html$,.*\.jsp$</param-value>
 </init-param>

In this example all HTTP requests except URL paths that end with with .html or .jsp will be candidates for routes. Both ONLY and EXCEPT must be a list of valid Java regular expressions or an exception will be thrown when the RoutesFilter is initialized.

Routes Engine

Both RoutesServlet and RoutesFilter use RoutesEngine to match HTTP requests to routes. It can be used directly to manually handle when routes should be used to process HTTP requests.

...
RoutesEngine routesEngine = new RoutesEngine();
...

if (routesEngine.process(servletRequest, servletResponse))
{
  // Request was processed, response has already been sent
}
else
{
 // Request was not processed, do something with the response
}
...

The Routing Table

The RoutingTable is where you tell Routes which of your classes are candidates for HTTP requests. After your objects have been added call the build() method to build the routing table. This method will throw a RoutesException if you have any invalid route configuration.

RoutingTable routingTable = new RoutingTable();
routingTable.add(new IndexRoutes(), new HomeRoutes(), new HelpRoutes()).build();

The RoutingTable should be treated as a singleton in your application. A static attribute will be set by the RoutingTable when constructed that the RoutingServlet and RoutingFilter will use when called.

You can either add your route class objects or instance objects to the RoutingTable.

// Both of these are acceptable
routingTable.add(IndexRoutes.class);
routingTable.add(new HomeRoutes());

If you add a class object then Routes will instantiate a new instance of this class (using the default constructor) for each matched request (the instantiation strategy can be controlled by using RouteInstancePool). If you add an instance object then that instance object will be used for every matched HTTP request. This means the route class must be thread safe.

If you are using Spring for dependency injection you can configure the RoutingTable using the setRoutes() method as:

<bean id="routingTable" class="org.baswell.routes.RoutingTable" init-method="build">
  <property name="routes">
    <list>
      <ref bean="loginRoutes"/>
      <ref bean="homeRoutes"/>
      <ref bean="helpRoutes"/>
    </list>
  </property>
</bean>

Routes By Example

Routes imposes no class hierarchies or interfaces on your classes. There are two ways to tell Routes how your Java objects are matched to HTTP requests, by convention or by using annotations. If your class has no Routes annotations then all public methods are candidates to being matched to incoming HTTP requests (only the immediate class, public methods of any super classes are not candidates). Routes use a convention for converting unannotated classes and methods to HTTP candidates that you can override using RouteByConvention.

The annotations Routes (class level) and Route (method level) provide full control over how methods are matched to HTTP requests. By default if your class has at least one of these annotations then only methods with the Route annotations can be matched to HTTP requests (public, unannotated methods will not be candidates). This can be override for the entire class with Routes.routeUnannotatedPublicMethods or globally with RoutesConfiguration.routeUnannotatedPublicMethods.

Example One: By Convention

public class LoginRoutes
{
  public String get(HttpServletRequest request)
  {
    ...
    return "login.jsp";
  }

  public void post(HttpServletRequest request, HttpServletResponse response)
  {...}

  public String getForgotPassword(HttpServletRequest request)
  {
    ...
    return "login/forgotpassword.jsp";
  }

  public String postForgotPassword(HttpServletRequest request)
  {
    ...
    throw new RedirectTo("/login");
  }
}
HTTP Request Method Called
GET /login HTTP/1.1
get(request)
By default the class name is used to form the first url segment, in this case /login. If the class name ends in Routes, Route, Controller, or Handler then this part of the class name will be removed from the path segment.Method names that just contain HTTP methods (ex. get, post) don't add anything to the matched path. The JSP file at /WEB-INF/jsps/login.jsp will be rendered to the user. You can change the root JSP directory with RoutesConfiguration.rootForwardPath
POST /login HTTP/1.1
post(request, response)
Since this method does not return anything, it must handle the content sent back to the user with the HttpServletResponse object.
GET /login/forgotpassword HTTP/1.1
getForgotPassword(request)
The remaining method name after all HTTP methods are removed from the begging forms the next url segment to match. The JSP file at _/WEB-INF/jsps/login.jsp_ will be rendered to the user.
/login/ForGotpasSworD HTTP/1.1
getForgotPassword(request)
By default matching in Routes for paths and parameters is case insensitive. This can be changed with RoutesConfiguration.caseInsensitve.
POST /login/forgotpassword HTTP/1.1
postForgotPassword(request)
You can use the helper class RedirectTo to redirect the client to another page. Note that when this exception is thrown any @AfterRoute methods will not be called. If you want the after route callbacks to still take place you can return a string starting with the key redirect: such as redirect:/login or call HttpServletResponse.sendRedirect directly.
PUT /login HTTP/1.1
404
Would need a put() method defined for this request to be matched. You can also combine HTTP methods together so for example the method postPut() would be called for both POST and PUT requests with the path /login

Example Two: Using Annotations

@Routes(forwardPath="login")
public class MyLoginRoutes
{
  @Route("/login")
  public String getLoginPage(HttpServletRequest request)
  {
    ...
    return "login.jsp";
  }

  @Route(value = "/login", respondsToMethods = {HttpMethod.POST, HttpMethod.PUT})
  public void doLogin(HttpServletRequest request, HttpServletResponse response)
  {...}

  @Route(value = "/login/forgotpassword", respondsToMethods = {HttpMethod.GET})
  public String showForgotPassword(HttpServletRequest request)
  {
    ...
    return "forgotpassword.jsp";
  }

  public void postForgotPassword(HttpServletRequest request)
  {
    ...
    throw new RedirectTo("/login");
  }
}
HTTP Request Method Called
GET /login HTTP/1.1
get(request)
If no root level path annotation is specified (Routes.value) then the path specified in Root.value will form the full match path. Since no Route.respondsToMethod was specified in the annotation the accepted HTTP methods are taken from the method name as in the convention based approach.
POST /login HTTP/1.1
doLogin(request, response)
PUT /login HTTP/1.1
doLogin(request, response)
Since both Routes.value and Route.respondsToMethod are specified the method name has no impact on which HTTP requests are matched.
GET /login/forgotpassword HTTP/1.1
showForgotPassword(request)
The JSP file at /WEB-INF/jsps/login/forgotpassword.jsp will be rendered to the user since Routes.forwardPath is specified. If the forward path does not begin with a / then the value is appended to RoutesConfiguration.rootForwardPath. If the forward path was /login then the the JSP file /login/forgotpassword.jsp would be rendered.
POST /forgotpassword HTTP/1.1
404
Since postForgotPassword is not annotated, it is not a candidate for HTTP requests. This can be overridden using Routes.routeUnannotatedPublicMethods or RoutesConfiguration.routeUnannotatedPublicMethods.

Example Three: Mixed

abstract public class BaseRoutes
{
  public String getSomeResource(HttpServletRequest request)
  {...}

  @Route(value="faq")
  public String getFAQ(HttpServletRequest request)
  {...}
}

@Routes(value="/login", routeUnannotatedPublicMethods=true, forwardPath="/login")
public class MyLoginRoutes extends BaseRoutes
{
  @Route
  public String getLoginPage(HttpServletRequest request)
  {
    ...
    return "login.jsp";
  }

  @Route(value = "/forgotpassword", respondsToMethods = {HttpMethod.GET})
  public String showForgotPassword(HttpServletRequest request)
  {
    ...
    return "forgotpassword.jsp";
  }

  public String postForgotPassword(HttpServletRequest request)
  {
    ...
    return "redirect:/login";
  }
}
HTTP Request Method Called
GET /login HTTP/1.1
get(request)
The root level path match is specified as /login in (Routes.value) since @Route doesn't specify a value, this is the full path matched for this method.
GET /login/forgotpassword HTTP/1.1
showForgotPassword(request)
The path specified @Route is appended onto the path specified in @Routes to form the full match path /login/forgotpassword. The JSP file at /login/forgotpassword.jsp will be rendered to the user since @Routes.forwardPath starts with a /.
POST /login/forgotpassword HTTP/1.1
postForgotPassword(request)
Since @Routes.routeUnannotatedPublicMethods is true, this public method is a candidate for HTTP requests. Both the path and accepted HTTP methods are taken by convention from the method name. Since @Routes.value is specified the path taken from this method name is appended to this value to form the full match path (/login/forgotpassword). If the returned string starts with redirect: then a redirect (302) will be returned to the client with text after the redirect: key sent as the URL to redirect to.
GET /login/someresource HTTP/1.1
404
GET /someresource HTTP/1.1
404
@Routes.routeUnannotatedPublicMethods only applies to the immediate class. Public, unannotated methods from super classes are not HTTP request candidates.
GET /login/faq HTTP/1.1
getFAQ(request)
Annotated super class methods are candidates for HTTP requests. If BaseRoutes declared a @Routes it would be ignored, @Routes is only used if present on the immediate class (MyLoginRoutes).

Example Four: Parameter Matching

The previous examples use path matching exclusively to determine how HTTP requests are mapped. Request parameters can also be used as a criteria for method matching.

@Routes("/api")
public class ApiRoutes
{
  @Route("/users?expired=true")
  public String getExpiredUsers(HttpServletRequest request)
  {
    ...
    return "expiredUsers.jsp";
  }

  @Route("/users?expired=false", defaultParameters="expired=false")
  public String getActiveUsers(HttpServletRequest request)
  {
    ...
    return "activeUsers.jsp";
  }

  @Route("/users?expired=false&admin=true", defaultParameters="expired=false")
  public String getActiveAdministrators(HttpServletRequest request)
  {
    ...
    return "activeAdministrators.jsp";
  }

  @Route("/users?expired=false&admin=true", defaultParameters="expired=false")
  public String postActiveAdministrators(HttpServletRequest request)
  {
    ...
    return "activeAdministrators.jsp";
  }
}

HTTP Request Method Called
GET /api/users?expired=true&mediaType=xml HTTP/1.1
getExpiredUsers(request)
Parameter matching is specified by adding a query string to the end @Route.value. All parameters specified in this query must be present and equal to the value specified for a match to be made on the method. Any additional parameters provided in the request that aren't specified in the query @Route.value query string will be ignored.
GET /api/users?expired=TRUE HTTP/1.1
getExpiredUsers(request)
By default matching in Routes for paths and parameters is case insensitive. This can be changed in RoutesConfiguration.caseInsensitve.
GET /api/users?mediaType=json HTTP/1.1
getActiveUsers(request)
If default values are specified for parameters using @Route.defaultParameters then the default parameter value will be used for the match comparison if the parameter is not provided in the request.
GET /api/users?expired=false&admin=true HTTP/1.1
getActiveAdministrators(
request)
Multiple parameters are delimited with &. Route methods with more parameter checks will be checked first which is why getActiveAdministrators is called here instead of getActiveUsers even though getActiveUsers is listed first.
POST /api/users HTTP/1.1
Content-Type: application/x-www-form-urlencoded

expired=false&active=true

postActiveAdministrators(
request)
Parameters can be specified from the query string or from the body content when form encoded.

Example Five: Pattern Matching

Routes supports the use of regular expressions for url path and parameter matching. To use a regular expression place the value between curly brackets {} such as /users/{\d+}. The value between these brackets must be a valid Java regular expressions with the following exceptions:

  • {*} Matches any value. Shortcut for {.*}.
  • {**} A double wildcard can only be used for url paths. It matches one or more url path segments of any value.
  • {} An empty set of brackets indicates that the regular expression is specified by a method parameter (next section).

The value matched against a path or parameter pattern can be passed in as a method parameter when the route method is invoked. The following basic Java types are supported for path or parameter values.

  • String
  • Character
  • char
  • Boolean
  • boolean
  • Byte
  • byte
  • Short
  • short
  • Integer
  • int
  • Long
  • long
  • Float
  • float
  • Double
  • double

Routes uses the standard parse methods (Boolean.parseBoolean, Integer.parseInt, Float.parseFlow) to coerce a pattern value into a method parameter. A String method parameter type will receive the matched value unmodified and a Character parameter will receive the first character of the matched value.

Routes matches patterns to method parameters by order. So for example in the route criteria /users/{\d+}/profile/{*} the first pattern {\d+} will map to the first method parameter that is one of the the types above. The second pattern {*} will match to the second method parameter of those types. You don't have to specify method parameters for each pattern but because they are assigned in order, if for example you want the value for the second pattern assigned to a method parameter you have to map the first pattern to a method parameter too.

@Routes("/users")
public class UserRoutes
{
  @Route("{\d+}")
  public String getUserByIdInPath(int userId, HttpServletRequest request)
  {...}

  @Route("?id={\d+}")
  public String getUserByIdInParamter(int userId, HttpServletRequest request)
  {...}

  @Route("/{*}")
  public String getUserByName(String userName, HttpServletRequest request)
  {...}

  @Route("/{\d+}/{.+-.+}")
  public String getShowUserProfileInPath(int userId, HttpServletRequest request)
  {...}

  @Route("/{\d+}?profileName={*}")
  public String getShowUserProfileInParameter(int userId,
                                              HttpServletRequest request,
                                              String profileName)
  {...}

  @Route("/{*}/changepassword", defaultParameters="expired=false")
  public String getChangePasswordById(int userId, HttpServletRequest request)
  {...}

  @Route("/{**}")
  public String getCustomUsersNotFoundPage(HttpServletRequest request)
  {...}
}
HTTP Request Method Called
GET /users/23 HTTP/1.1
getUserByIdInPath(23, request)
The regular expression {\d+} will match any numeric value. The numeric value matched will be provided in the userId parameter when invoked. If this method had no Integer parameter the HTTP request would still be matched and the method would be invoked without the value being provided.
GET /users?id=23 HTTP/1.1
getUserByIdInParamter(23, request)
Regular expression for parameters work the same as paths.
GET /users?id=baswerc HTTP/1.1
404
The HTTP request does match any Routes because baswerc does not match the pattern {\d+}.
GET /users/baswerc HTTP/1.1
getUserByName("baswerc", request)
GET /users/23A HTTP/1.1
getUserByName("23A", request)
GET /users HTTP/1.1
404
GET /users/ HTTP/1.1
404
The wildcard pattern {*} will match against any value. In this example any value in the segment after /users that is not numeric will be matched to this method (since the numeric pattern method getUsersByIdInPath comes first in the class declaration it takes precedence). Note a wildcard declaration in a path or parameter will match against any value but the value must present (empty is not a match.
GET /users/23/basic-blue HTTP/1.1
getShowUserProfileInPath(23, request)
Pattern values are supplied as method parameters in the order they were specified. You don't have to specify method parameters for all patterns in the route.
GET /users/23?profile=basic-blue HTTP/1.1
getShowUserProfileInParameter(23,
request, "profile-basic")
Pattern value parameters can be intermingled with the other allowed route method parameter types (ex. HttpServletRequest).
GET /users/23/changepassword HTTP/1.1
getChangePasswordById(23, request)
GET /users/baswerc/changepassword HTTP/1.1
throw new RoutesException()
Routes does not try to verify that path or parameter patterns are correctly mapped to method parameter types. If a path or parameter value cannot be coerced into method parameter value (ex. NumberFormatException) a RoutesException will be thrown and the request will end in error. Method parameter patterns discussed in this section can help with this.
GET /users/something/else/goes/here HTTP/1.1
getCustomUsersNotFoundPage(request)
GET /users/23/abc HTTP/1.1
getCustomUsersNotFoundPage(request)
GET /users?one=1 HTTP/1.1
getCustomUsersNotFoundPage(request)
The double wildcard will match any arbitrary number of path segments that aren't matched by other criteria. Since the double wildcard can consume multiple path segments it will not match to any method parameters.

Example Six: Method Parameter Patterns

Method parameter patterns are specified by an empty set of curly brackets {}. The regular expression used for these is inferred from the method parameter this pattern is mapped to. The method parameter must be one of the standard types defined in the previous section (Boolean, Byte, Short, Integer, Long, etc.) and it must be present in the method declaration. An exception will be thrown when {} is used in the route criteria and there is no matching method parameter. For example the following routes class is invalid.

public class InvalidRoutes
{
  // {} matches to the method parameter id
  @Route("/valid/{}") 
  public String getValidRoute(int id, HttpServletRequest request)
  {...}

  // No matching method parameter to specify what regular expression is used
  @Route("/invalid/{}") 
  public String getInvalidRoute(HttpServletRequest request)
  {...}
}

The following will result in a RoutesException being thrown routingTable.add(InvalidRoutes.class).build().

@Routes("/users")
public class UserRoutes
{
  @Route("{}?showDetails={}")
  public String getUserByIdInPath(int userId, 
                                  boolean showDetails, 
                                  HttpServletRequest request)
  {...}

  @Route("?id={}")
  public String getUserByIdInParamter(int userId, HttpServletRequest request)
  {...}

  @Route("/{}")
  public String getUserByName(String userName, HttpServletRequest request)
  {...}

  @Route("/{}/{.+-.+}")
  public String getShowUserProfileInPath(int userId,
                                         String profile,
                                         HttpServletRequest request)
  {...}

  @Route("/{}?profileName={}")
  public String getShowUserProfileInParameter(int userId,
                                              HttpServletRequest request,
                                              String profileName)
  {...}
}
HTTP Request Method Called
GET /users/23?showDetails=true HTTP/1.1
getUserByIdInPath(23, true, request)
Since userId is of type int, the first pattern used with match any value that can be coerced into a int. For the boolean type showDetails, the pattern used will match any (case insentive) value of true or false.
GET /users?id=23 HTTP/1.1
getUserByIdInParamter(23, request)
GET /users?id=baswerc HTTP/1.1
404
Method parameter patterns can be used for url path and parameter matching. "baswerc" cannot be coerced into an int so a match is not made.
GET /users/baswerc HTTP/1.1
getUserByName("baswerc", request)
String method parameters will match any value. It's the same as using the wildcard pattern {*}.
GET /users/23/basic-blue HTTP/1.1
getShowUserProfileInPath(23,
"basic-blue", request)
Method parameter pattern and explicit patterns can be mixed.
GET /users/23?profile=basic-blue HTTP/1.1
getShowUserProfileInParameter(23,
request, "profile-basic")
Method pattern parameters can be mix in with other allowed parameter types.

Example Six: Responding To Media Type Requests

An additional, optional criteria that can be placed on Route methods is the MediaType the client is expecting for the response. @Route.responsesToMediaRequests is used to specify which type of media this route method is capable of serving. There are three different ways that Routes determines the type of media the client is expecting in the response. These are listed in the order of precedence.

  • The value of the mediaType parameter (if present). For example the url request /api/users?mediaType=xml will map to XML
  • File extension (if present) of the URL path. For example a request for /test.pdf will map to PDF.
  • The value of the Accept header (if present). For example the header Accept: application/json will map to JSON.
@Routes("/api")
public class APIRoutes
{
  @Route(value="/users", respondsToMediaRequests=MediaType.JSON)
  public String getUsersInJSON(HttpServletRequest request)
  {...}

  @Route(value="/users", respondsToMediaRequests=MediaType.XML)
  public String getUsersInXML(HttpServletRequest request)
  {...}

  @Route(value="/users", respondsToMediaRequests={MediaType.HTML, MediaType.PDF})
  public String getUsersInHTMLOrPDF(HttpServletRequest request)
  {...}

  @Route(value="/users/{*}", respondsToMediaRequests=MediaType.PDF)
  public String getUsersReportPDF(String reportName, HttpServletRequest request)
  {...}
}
HTTP Request Method Called
GET /users?mediaType=json HTTP/1.1
getUsersInJSON(request)
GET /users HTTP/1.1
Accept: application/json
getUsersInJSON(request)
GET /users HTTP/1.1
Accept: application/xhtml+xml
404
The value of the mediaType parameter should be the value of the MediaType enumeration. The value of the Accept header should be one of the MIMETypes. If none of the media criteria matches then the route method will not be matched
GET /users?mediaType=xml HTTP/1.1
Accept: application/json
getUsersInXML(request)
The mediaType parameter takes precedence over the Accept header.
GET /users HTTP/1.1
Accept: application/xhtml+xml
getUsersInHTMLOrPDF(request)
GET /users HTTP/1.1
Accept: application/pdf
getUsersInHTMLOrPDF(request)
If multiple MediaType are specified in the route critieria then a match will be made if any of the specified MediaTypes are requested.
GET /users/active.pdf HTTP/1.1
       
getUsersReportPDF("active.pdf",
request)
GET /users/active.pdf?mediaType=xml HTTP/1.1
       
404
The mediaType parameter takes precedence over the file extension.

Route Method Parameters

Your route methods can have the following parameter types.

If a route method declares any other parameter types then a RoutesException will be thrown when the RoutingTable is built.

Request Content

You can process the content a client sends in the request by using RequestContent as a parameter to your route method. When you define the RequestContent container you should specify the class that the request content will be mapped to.

@Routes("/api")
public class APIRoutes()
{
  @Route(expectedRequestMediaType = MediaType.JSON)
  public void putUser(RequestContent<User> requestContent) throws IOException
  {
    User user = requestContent.get();
    ...
  }
}

In this example Routes will try to map the submitted request content to the specified User class. Routes current supports the following libraries for automatic conversion of request content.

To automatically convert the request content, Routes must determine the content type of request. The request content type is determined in the following order.

  1. If the type of RequestContent requires a certain content type then Routes will use that content type. For example RequestContent<org.json.simple.JSONObject> dictates that the request content is JSON and RequestContent<org.w3c.Node> dictates the request content is XML.
  2. If Route.expectedRequestMediaType is specified then Routes will use that content type.
  3. If the Content-Type header is set it the request, Routes will use this content type.
  4. Routes will (very rudimentary) try to guess the content type from the content submitted.

Response Content

If your routes method returns something other than String, Routes will try to determine the type of the object and the correction response action based upon that type. For example if the routes method returns a org.json.simple.JSONObject, Routes will set the content type of the response (if not already set) to application/json and send toJSONString() as the response of the HTTP request.

For libraries such as GSON and Jackson that operate on plain Java objects, you can give a hint to Routes on how to convert the returned object by using @Route.contentType.

@Routes("/api")
public class APIRoutes()
{
  // If GSON is available on your application's classpath, then Routes will send back the
  // users list as servletResponse.getWriter().write(new Gson().toJson(users));
  //
  // Otherwise if Jackson is available on your application's classpath then Routes will
  // send back the users list as new
  // ObjectMapper().writeValue(servletResponse.getWriter(), users);
  @Route(value="/users", contentType = MIMETypes.JSON)
  public List<User> getUsers()
  {
    ...
    return users;
  }
}

If Routes can not figure out how to convert your complex object it will simple call toString() on the returned object and return that as the content of the HTTP response.

Routes currently supports the following libraries for automatic conversion.

Pre & Post Route Events

You can tell Routes to make calls before and after a route method is invoked using the @BeforeRoute and @AfterRoute annotations. Methods with these annotations must be in the same class hiearchy as the route method to be invoked.

@BeforeRoute and @AfterRoute methods can use all the same method parameter types as route methods except the criteria pattern parameters (Integer, double, etc.) are not allowed.

abstract class AuthenticatedRoutes
{
  @BeforeRoute(exceptTags="anonymousAllowed")
  public void requireLoggedIn(HttpServletRequest request)
  {
    ...
    if (!loggedIn)
    {
      // Request processing will end here, route method will
      // not be invoked.
      throw new RedirectTo("/login");
    }
  }

  @AfterRoute(exceptTags="anonymousAllowed")
  public void afterLoggedInRequest(URL url)
  {
    log.info("Logged in request: " + url);
  }
}

@Routes("/")
public class HomeRoutes extends AuthenticatedRoutes
{
  // BeforeRoute and AfterRoute methods called.
  @Route
  public String get(HttpServletRequest request)
  {...}

  // BeforeRoute and AfterRoute methods not called.
  @Route(value="/faq", tags="anonymousAllowed")
  public String getFAQ()
  {...}
}

Routes Configuration

All of configuration for Routes is contained in RoutesConfiguration. If you need to customize the default configuration then pass in this object to the RoutingTable upon creation.

RoutesConfiguration configuration = new RoutesConfiguration();
configuration.routeUnannotatedPublicMethods = true
configuration.rootForwardPath = "/jsps";

RoutingTable routingTable = new RoutingTable(configuration);
...

Routes Meta Page

Routes can serve up a web page that allows you test various URL paths, parameter and media type combinations to see which of your route methods will be selected. To enable this tool specify the path for the meta page with RoutesConfiguration.routesMetaPath. You must make sure this meta page does not collide with any of your route method criteria (the route methods will always win when this happens). If you want to deploy this utility in a non-development environment you can enable authentication and authorization for this utility using MetaAuthenticator.

Image of Meta Page

Additional Documentation

Developed By

Corey Baswell - [email protected]

License

Copyright 2015 Corey Baswell

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Versions

Version
1.3
1.2
1.1
1.0