Red Hat Training

A Red Hat training course is available for Red Hat Fuse

4.2. Defining Services with REST DSL

REST DSL is a facade

The REST DSL is effectively a facade that provides a simplified syntax for defining REST services in a Java DSL or an XML DSL (Domain Specific Language). REST DSL does not actually provide the REST implementation, it is just a wrapper around an existing REST implementation (of which there are several in Apache Camel).

Advantages of the REST DSL

The REST DSL wrapper layer offers the following advantages:
  • A modern easy-to-use syntax for defining REST services.
  • Compatible with multiple different Apache Camel components.
  • Swagger integration (through the camel-swagger component).

Components that integrate with REST DSL

Because the REST DSL is not an actual REST implementation, one of the first things you need to do is to choose a Camel component to provide the underlying implementation. The following Camel components are currently integrated with the REST DSL:
Note
The Rest component (part of camel-core) is not a REST implementation. Like the REST DSL, the Rest component is a facade, providing a simplified syntax to define REST services using a URI syntax. The Rest component also requires an underlying REST implementation.

Configuring REST DSL to use a REST implementation

To specify the REST implementation, you use either the restConfiguration() builder (in Java DSL) or the restConfiguration element (in XML DSL). For example, to configure REST DSL to use the Spark-Rest component, you would use a builder expression like the following in the Java DSL:
restConfiguration().component("spark-rest").port(9091);
And you would use an element like the following (as a child of camelContext) in the XML DSL:
<restConfiguration component="spark-rest" port="9091"/>

Syntax

The Java DSL syntax for defining a REST service is as follows:
rest("BasePath").Option()+.
    .Verb("Path").Option()+.[to() | route().CamelRoute.endRest()]
    .Verb("Path").Option()+.[to() | route().CamelRoute.endRest()]
    ...
    .Verb("Path").Option()+.[to() | route().CamelRoute];
Where CamelRoute is an optional embedded Camel route (defined using the standard Java DSL syntax for routes).
The REST service definition starts with the rest() keyword, followed by one or more verb clauses that handle specific URL path segments. The HTTP verb can be one of get(), head(), put(), post(), delete(), or verb(). Each verb clause can use either of the following syntaxes:
  • Verb clause ending in to() keyword. For example:
    get("...").Option()+.to("...")
  • Verb clause ending in route() keyword (for embedding a Camel route). For example:
    get("...").Option()+.route("...").CamelRoute.endRest()

REST DSL with Java

In Java, to define a service with the REST DSL, put the REST definition into the body of a RouteBuilder.configure() method, just like you do for regular Apache Camel routes. For example, to define a simple Hello World service using the REST DSL with the Spark-Rest component, define the following Java code:
restConfiguration().component("spark-rest").port(9091);

rest("/say")
    .get("/hello").to("direct:hello")
    .get("/bye").to("direct:bye");
 
from("direct:hello")
    .transform().constant("Hello World");
from("direct:bye")
    .transform().constant("Bye World");
The preceding example features three different kinds of builder:
restConfiguration()
Configures the REST DSL to use a specific REST implementation (Spark-Rest).
rest()
Defines a service using the REST DSL. Each of the verb clauses are terminated by a to() keyword, which forwards the incoming message to a direct endpoint (the direct component splices routes together within the same application).
from()
Defines a regular Camel route.

REST DSL with XML

In XML, to define a service with the XML DSL, define a rest element as a child of the camelContext element. For example, to define a simple Hello World service using the REST DSL with the Spark-Rest component, define the following XML code (in Blueprint):
<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  <restConfiguration component="spark-rest" port="9091"/>

  <rest path="/say">
    <get uri="/hello">
      <to uri="direct:hello"/>
    </get>
    <get uri="/bye">
      <to uri="direct:bye"/>
    </get>
  </rest>

  <route>
    <from uri="direct:hello"/>
    <transform>
      <constant>Hello World</constant>
    </transform>
  </route>
  <route>
    <from uri="direct:bye"/>
    <transform>
      <constant>Bye World</constant>
    </transform>
  </route>
</camelContext>

Specifying a base path

The rest() keyword (Java DSL) or the path attribute of the rest element (XML DSL) allows you to define a base path, which is then prefixed to the paths in all of the verb clauses. For example, given the following snippet of Java DSL:
rest("/say")
    .get("/hello").to("direct:hello")
    .get("/bye").to("direct:bye");
Or given the following snippet of XML DSL:
<rest path="/say">
  <get uri="/hello">
    <to uri="direct:hello"/>
  </get>
  <get uri="/bye" consumes="application/json">
    <to uri="direct:bye"/>
  </get>
</rest>
The REST DSL builder gives you the following URL mappings:
/say/hello
/say/bye
The base path is optional. If you prefer, you could (less elegantly) specify the full path in each of the verb clauses:
rest()
    .get("/say/hello").to("direct:hello")
    .get("/say/bye").to("direct:bye");

Using Dynamic To

The REST DSL supports the toD dynamic to parameter. Use this parameter to specify URIs.
For example, in JMS a dynamic endpoint URI could be defined in the following way:
public void configure() throws Exception {
   rest("/say")
     .get("/hello/{language}").toD("jms:queue:hello-${header.language}");
}
In XML DSL, the same details would look like this:
<rest uri="/say">
  <get uri="/hello//{language}">
    <toD uri="jms:queue:hello-${header.language}"/>
  </get>
<rest>
For more information about the toD dynamic to parameter, see the section called “Dynamic To”.

URI templates

In a verb argument, you can specify a URI template, which enables you to capture specific path segments in named properties (which are then mapped to Camel message headers). For example, if you would like to personalize the Hello World application so that it greets the caller by name, you could define a REST service like the following:
rest("/say")
    .get("/hello/{name}").to("direct:hello")
    .get("/bye/{name}").to("direct:bye");

from("direct:hello")
    .transform().simple("Hello ${header.name}");
from("direct:bye")
    .transform().simple("Bye ${header.name}");
The URI template captures the text of the {name} path segment and copies this captured text into the name message header. If you invoke the service by sending a GET HTTP Request with the URL ending in /say/hello/Joe, the HTTP Response is Hello Joe.

Embedded route syntax

Instead of terminating a verb clause with the to() keyword (Java DSL) or the to element (XML DSL), you have the option of embedding an Apache Camel route directly into the REST DSL, using the route() keyword (Java DSL) or the route element (XML DSL). The route() keyword enables you to embed a route into a verb clause, with the following syntax:
RESTVerbClause.route("...").CamelRoute.endRest()
Where the endRest() keyword (Java DSL only) is a necessary punctuation mark that enables you to separate the verb clauses (when there is more than one verb clause in the rest() builder).
For example, you could refactor the Hello World example to use embedded Camel routes, as follows in Java DSL:
rest("/say")
    .get("/hello").route().transform().constant("Hello World").endRest()
    .get("/bye").route().transform().constant("Bye World");
And as follows in XML DSL:
<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  ...
  <rest path="/say">
    <get uri="/hello">
      <route>
        <transform>
          <constant>Hello World</constant>
        </transform>
      </route>
    </get>
    <get uri="/bye">
      <route>
        <transform>
          <constant>Bye World</constant>
        </transform>
      </route>
    </get>
  </rest>
</camelContext>
Note
If you define any exception clauses (using onException()) or interceptors (using intercept()) in the current CamelContext, these exception clauses and interceptors are also active in the embedded routes.

Specifying the content type of requests and responses

You can filter the content type of HTTP requests and responses using the consumes() and produces() options in Java, or the consumes and produces attributes in XML. For example, some common content types (officially known as Internet media types) are the following:
  • text/plain
  • text/html
  • text/xml
  • application/json
  • application/xml
The content type is specified as an option on a verb clause in the REST DSL. For example, to restrict a verb clause to accept only text/plain HTTP requests, and to send only text/html HTTP responses, you would use Java code like the following:
rest("/email")
    .post("/to/{recipient}").consumes("text/plain").produces("text/html").to("direct:foo");
And in XML, you can set the consumes and produces attributes, as follows:
<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  ...
  <rest path="/email">
    <post uri="/to/{recipient}" consumes="text/plain" produces="text/html">
      <to "direct:foo"/>
    </get>
  </rest>
</camelContext>
You can also specify the argument to consumes() or produces() as a comma-separated list. For example, consumes("text/plain, application/json").

Additional HTTP methods

Some HTTP server implementations support additional HTTP methods, which are not provided by the standard set of verbs in the REST DSL, get(), head(), put(), post(), delete(). To access additional HTTP methods, you can use the generic keyword, verb(), in Java DSL and the generic element, verb, in XML DSL.
For example, to implement the TRACE HTTP method in Java:
rest("/say")
    .verb("TRACE", "/hello").route().transform();
Where transform() copies the body of the IN message to the body of the OUT message, thus echoing the HTTP request.
To implement the TRACE HTTP method in XML:
<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  ...
  <rest path="/say">
    <verb uri="/hello" method="TRACE">
      <route>
        <transform/>
      </route>
    </get>
</camelContext>

Defining custom HTTP error messages

If your REST service needs to send an error message as its response, you can define a custom HTTP error message as follows:
  1. Specify the HTTP error code by setting the Exchange.HTTP_RESPONSE_CODE header key to the error code value (for example, 400, 404, and so on). This setting indicates to the REST DSL that you want to send an error message reply, instead of a regular response.
  2. Populate the message body with your custom error message.
  3. Set the Content-Type header, if required.
  4. If your REST service is configured to marshal to and from Java objects (that is, bindingMode is enabled), you should ensure that the skipBindingOnErrorCode option is enabled (which it is, by default). This is to ensure that the REST DSL does not attempt to unmarshal the message body when sending the response.
    For more details about object binding, see Section 4.3, “Marshalling to and from Java Objects”.
The following Java example shows how to define a custom error message:
// Java
// Configure the REST DSL, with JSON binding mode
restConfiguration().component("restlet").host("localhost").port(portNum).bindingMode(RestBindingMode.json);

// Define the service with REST DSL
rest("/users/")
    .post("lives").type(UserPojo.class).outType(CountryPojo.class)
        .route()
            .choice()
                .when().simple("${body.id} < 100")
                    .bean(new UserErrorService(), "idTooLowError")
                .otherwise()
                    .bean(new UserService(), "livesWhere");
In this example, if the input ID is a number less than 100, we return a custom error message, using the UserErrorService bean, which is implemented as follows:
// Java
public class UserErrorService {
    public void idTooLowError(Exchange exchange) {
        exchange.getIn().setBody("id value is too low");
        exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "text/plain");
        exchange.getIn().setHeader(Exchange.HTTP_RESPONSE_CODE, 400);
    }
}
In the UserErrorService bean we define the custom error message and set the HTTP error code to 400.

Parameter Default Values

Default values can be specified for the headers of an incoming Camel message.
You can specify a default value by using a key word such as verbose on the query parameter. For example, in the code below, the default value is false. This means that if no other value is provided for a header with the verbose key, false will be inserted as a default.
rest("/customers/")
    .get("/{id}").to("direct:customerDetail")
    .get("/{id}/orders")
      .param()
	.name("verbose")
	.type(RestParamType.query)
	.defaultValue("false")
	.description("Verbose order details")
      .endParam()
        .to("direct:customerOrders")
    .post("/neworder").to("direct:customerNewOrder");

Wrapping a JsonParserException in a custom HTTP error message

A common case where you might want to return a custom error message is in order to wrap a JsonParserException exception. For example, you can conveniently exploit the Camel exception handling mechanism to create a custom HTTP error message, with HTTP error code 400, as follows:
// Java
onException(JsonParseException.class)
    .handled(true)
    .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400))
    .setHeader(Exchange.CONTENT_TYPE, constant("text/plain"))
    .setBody().constant("Invalid json data");

REST DSL options

In general, REST DSL options can be applied either directly to the base part of the service definition (that is, immediately following rest()), as follows:
rest("/email").consumes("text/plain").produces("text/html")
    .post("/to/{recipient}").to("direct:foo")
    .get("/for/{username}").to("direct:bar");
In which case the specified options apply to all of the subordinate verb clauses. Or the options can be applied to each individual verb clause, as follows:
rest("/email")
    .post("/to/{recipient}").consumes("text/plain").produces("text/html").to("direct:foo")
    .get("/for/{username}").consumes("text/plain").produces("text/html").to("direct:bar");
In which case the specified options apply only to the relevant verb clause, overriding any settings from the base part.
Table 4.1, “REST DSL Options” summarizes the options supported by the REST DSL.

Table 4.1. REST DSL Options

Java DSLXML DSLDescription
bindingMode() @bindingMode Specifies the binding mode, which can be used to marshal incoming messages to Java objects (and, optionally, unmarshal Java objects to outgoing messages). Can have the following values: off (default), auto, json, xml, json_xml.
consumes() @consumes Restricts the verb clause to accept only the specified Internet media type (MIME type) in a HTTP Request. Typical values are: text/plain, text/http, text/xml, application/json, application/xml.
customId() @customId Defines a custom ID for JMX management.
description() description Document the REST service or verb clause. Useful for JMX management and tooling.
enableCORS() @enableCORS If true, enables CORS (cross-origin resource sharing) headers in the HTTP response. Default is false.
id() @id Defines a unique ID for the REST service, which is useful to define for JMX management and other tooling.
method() @method Specifies the HTTP method processed by this verb clause. Usually used in conjunction with the generic verb() keyword.
outType() @outType When object binding is enabled (that is, when bindingMode option is enabled), this option specifies the Java type that represents a HTTP Response message.
produces() produces Restricts the verb clause to produce only the specified Internet media type (MIME type) in a HTTP Response. Typical values are: text/plain, text/http, text/xml, application/json, application/xml.
type() @type When object binding is enabled (that is, when bindingMode option is enabled), this option specifies the Java type that represents a HTTP Request message.
VerbURIArgument @uri Specifies a path segment or URI template as an argument to a verb. For example, get(VerbURIArgument).
BasePathArgument @path Specifies the base path in the rest() keyword (Java DSL) or in the rest element (XML DSL).