Skip navigation links

Package org.apache.juneau.rest

REST Servlet API

See: Description

Package org.apache.juneau.rest Description

REST Servlet API

Defines an API for defining REST resources as servlets.

Table of Contents
  1. Introduction

  2. Hello World Example

  3. Class Hierarchy

  4. REST Servlets

    1. REST Java Method Signature

      1. Path

      2. Matchers

    2. Request Content

      1. Form Posts

      2. Multipart Form Posts

    3. Response Content

    4. Lifecycle Hooks

    5. OPTIONS Pages

    6. Serializers

    7. Parsers

    8. Properties

    9. Transforms

    10. Guards

    11. Converters

    12. Child Resources

    13. Localized Messages

    14. Encoders

    15. SVL Vars

    16. Static Files

    17. Listener Methods

    18. Stylesheet

    19. Default Headers

    20. Handling Errors / Logging

    21. Configuration Files

    22. Annotation Inheritance

    23. HTTP Status Codes

    24. Overloaded HTTP Methods

    25. Built-In Parameters

    26. Defining your own serializers/parsers

    27. Response Handlers

    28. Other Notes

  5. Using with OSGi

  6. POJOs Convertible From Strings

  7. Address Book Resource

1 - Introduction

The juneau-rest.jar library allows you to quickly wrap POJOs and expose them as full-fledged REST resources served up in a servlet container using a bare-minimum amount of code. The primary goal for Juneau was to make it as easy as possible to implement easy-to-read and self-documenting REST resources using very little code.

One of the biggest advantages of the Juneau REST framework over similar architectures is that it hides the serialization layer from the developer. The developer can work entirely with POJOs and let the Juneau framework handle all the serialization and parsing work. The developer need never know what the Accept or Content-Type or Accept-Encoding (etc...) header values are because those details are all handled by the framework.

The API builds upon the existing JEE Servlet API. The root class, RestServlet is nothing but a specialized HttpServlet, and the RestRequest and RestResponse classes are nothing more than specialized HttpServletRequest and HttpServletResponse objects. This allows maximum flexibility for the developer since you can let Juneau handle operations such as serialization, or you can revert to the existing servlet APIs to do low-level processing of requests yourself. It also means you need nothing more than a Servlet container such as Jetty to use the REST framework.

Features
  • Serializes POJOs to JSON, XML, HTML, URL-Encoding, UON, RDF/XML, N-Triple, Turtle, N3, SOAP, or Java-serialized-object based on value of Accept header.
    No user code is required to handle these types.
    • Extensible design that provides ability to override existing content type handlers, or add the ability to handle other kinds of content types.
  • Parses content of POST/PUT request bodies to POJOs.
  • Automatic built-in ability to serialize POJO metadata to JSON+SCHEMA, XML+SCHEMA, or HTML+SCHEMA based on Accept header.
  • Automatic negotiation of output Writer based on HTTP headers.
    • Automatic handling of Accept-Charset header for all character sets supported by the JVM.
    • Automatic handling of Accept-Encoding header with registered encoders.
  • Automatic error handling.
    • Automatic 401 errors (Unauthorized) on failed guards.
    • Automatic 404 errors (Not Found) on unmatched path patterns.
    • Automatic 405 errors (Method Not Implemented) on unimplemented methods.
    • Automatic 406 errors (Not Acceptable) when no matching serializer was found to handle the Accept header.
    • Automatic 412 errors (Precondition Failed) when all matchers failed to match.
    • Automatic 415 errors (Unsupported Media Type) when no matching parser was found was found to handle the Content-Type header.
    • Automatic 500 errors on uncaught exceptions.
  • Self-documenting REST interfaces.
  • Various useful debugging features that make debugging using a browser extremely simple...
    • Ability to pass HTTP header values as URL GET parameters (e.g. &Accept=text/xml).
    • Ability to pass HTTP content on PUT/POST requests as a URL GET parameter (e.g. &content={foo:"bar"}).
    • Ability to simulate non-GET requests using a &method GET parameter (e.g. &method=POST).
    • Ability to force "text/plain" on response using GET parameter &plainText=true.
  • Ability to implement overloaded HTTP methods through the use of the &method attribute (e.g. &method=FOO).
  • Ability to match URL patterns (e.g. /foo/{fooId}/bar/{barId}) against URLs (e.g. /foo/123/bar/456/bing).
  • Ability to associate guards at the resource or method levels through annotations.
    Typically useful for security, but can be used for a variety of purposes.
  • Ability to associate converters at the resource or method levels through annotations.
    Typically useful for performing conversions on input and output, such as for supporting older input and output formats.

Many of the examples in this document are pulled directly from the microservice-samples-project.zip project.

2 - Hello World Example

A REST resource is an implementation of RestServlet, which itself is simply an extension of HttpServlet.

In this example, we define a resource called HelloWorldResource. This example is located in the microservice-samples-project.zip project. It's assumed the reader is familiar with defining servlets in web applications.

Like any servlet, we could define our resource in the web.xml file of the web application like so...

<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.3"> <servlet> <servlet-name>HelloWorldResource</servlet-name> <servlet-class>com.foo.sample.HelloWorldResource</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorldResource</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>

Our servlet code is shown below:

/** * Sample REST resource that prints out a simple "Hello world!" message. */ @RestResource( messages="nls/HelloWorldResource", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ) ) public class HelloWorldResource extends Resource { /** GET request handler */ @RestMethod(name=GET, path="/*") public String sayHello() { return "Hello world!"; } }

The messages annotation points to a properties file on the classpath whose contents are shown below:

#-------------------------------------------------------------------------------- # HelloWorldResource labels #-------------------------------------------------------------------------------- label = Hello World sample resource description = Simplest possible resource sayHello = Responds with "Hello world!"

It doesn't much simpler than that. In this case, we're simply returning a string that will be converted to any of the supported languages (e.g. JSON, XML, HTML, ...). However, we could have returned any POJO consisting of beans, maps, collections, etc...

The RestServletDefault class that we're using here is a subclass of RestServlet that provides default support for a variety of content types. Implementers can choose to use this class, or create their own subclass of RestServlet with their own specialized serializers and parsers.

If you were to start up this servlet and view it with a browser, you would see this:

The Juneau REST interface is designed to make it easy to interact with resources using nothing but a browser. Therefore, several built-in features are provided for making it easy to do so. Specifically, we'll be using these available URL parameters...

  • &plainText=true - If specified, then the Content-Type on the response is always "text/plain" regardless of the data format.
  • &Accept=X - Specify the content type of the response. In a browser, "text/html" is the default content type, but this parameter can be used to override the content type on the response.
    Note: The behavior is identical to setting the Accept header on the request. In fact, Juneau allows ANY HTTP request headers to be specified as URL parameters for debugging purposes.

Using the plainText parameter, we can view the HTML as plain text...

You'll notice that the HTML view has a simple stylesheet associated with it to improve the look of the interface. It is possible to specify your own stylesheet, but the default styles will usually suffice for most purposes.

When accessed through a browser, the content type will default to HTML (based on the value of the Accept HTTP header).

Let's use the &Accept URL parameter to override the Accept HTTP header to view this servlet in other formats...

In the case of JSON, we're serialize a single string, so it gets rendered as a JSON fragment....

...or as XML...

...or any of the other supported languages.

If you click the OPTIONS link on the page, you'll see the results from an HTTP OPTIONS request:

The OPTIONS page is a serialized Swagger DTO bean populated by introspection of the class itself combined with labels in the messages properties file and annotations. It's composed of a POJO that gets serialized just like any other POJO. Therefore, the POJO can be serialized to any of the supported languages, like Swagger JSON.

3 - Class Hierarchy

The class hierarchy for the REST servlet class is shown below:

The servlets with RDF support require Jena on the classpath. All other serializers and parsers do not have any external library dependencies. For this reason, we have separate servlets for supporting RDF so that you don't need Jena if you don't need to support RDF.

The RestRequest and RestResponse classes described later also extend from their servlet equivalents:

4 - REST Servlets

Since REST servlets are subclasses of HttpServlet, they can be deployed in a J2EE container like any other servlet, typically inside a web.xml file. The REST servlet framework does not depend on any classloader scanning or external setup other than registering the servlet with the J2EE container.

REST servlets can also be deployed by declaring them as children of other REST servlets (described later).

A REST servlet consists of an instance of RestServlet annotated with @RestResource containing public Java methods annotated with @RestMethod.

Developers will typically subclass directly from RestServletDefault since it provides a default set of serializers and parsers for a variety of Accept and Content-Type types.

Valid Accept headers for RestServletDefault
Accept Content-Type Serializer
application/json
text/json
application/json JsonSerializer
application/json+simple
text/json+simple
application/json JsonSerializer.Simple
application/json+schema
text/json+schema
application/json JsonSchemaSerializer
text/xml text/xml XmlDocSerializer
text/xml+schema text/xml XmlSchemaDocSerializer
text/html text/html HtmlDocSerializer
text/html+stripped text/html HtmlStrippedDocSerializer
text/uon text/uon UonSerializer
application/x-www-form-urlencoded application/x-www-form-urlencoded UrlEncodingSerializer
text/xml+soap text/xml SoapXmlSerializer
text/plain text/plain PlainTextSerializer
application/x-java-serialized-object application/x-java-serialized-object JsoSerializer
Valid Content-Type headers for RestServletDefault
Content-Type Parser
application/json
text/json
JsonParser
text/xml
application/xml
XmlParser
text/html
text/html+stripped
HtmlParser
text/uon UonParser
application/x-www-form-urlencoded UrlEncodingParser
text/plain PlainTextParser

RestServletDefault also provides a default OPTIONS page by implementing a RestServletDefault.getOptions(RestRequest) method that returns a POJO consisting of beans describing the class. This is what produces the output for the OPTIONS page on the Hello World sample above.

Additional Information

4.1 - REST Java Method Signature

REST Java methods are identified on REST servlets using the @RestMethod annotation. The annotation allows the framework to identify the available REST methods through reflection.

/** GET request handler */ @RestMethod(name=GET, path="/") public String sayHello() { return "Hello world!"; }

Method Name

There are no restrictions on the name of the Java method. However, if you plan on making use of the @RestResource.messages() annotation (described later), the method names must be unique to make it possible to identify unique keys for labels in the resource bundle. Therefore, you should not define two identically-named doFoo(...) methods that differ only by parameters. If you're not using messages for NLS support, then name them whatever you want!

Method Return Type

The return type can be any serializable POJO as defined in POJO Categories. It can also be void if the method is not sending any output (e.g. a request redirect) or is setting the output using the RestResponse.setOutput(Object) method. Calling the RestResponse.setOutput(Object) method is functionally equivalent to returning a value.

// Equivalent method 1 @RestMethod(name=GET) public void doGet(RestResponse res) { res.setOutput("Hello World!"); } // Equivalent method 2 @RestMethod(name=GET) public String doGet() { return "Hello World!"; }

The return type can also be any of the following special object types:

  • InputStream
    The contents are simply piped to the output stream returned by RestResponse.getNegotiatedOutputStream().
    Note that you should call ServletResponseWrapper.setContentType(String) to set the Content-Type header if you use this object type.
  • Reader
    The contents are simply piped to the output stream returned by RestResponse.getNegotiatedWriter().
    Note that you should call ServletResponseWrapper.setContentType(String) to set the Content-Type header if you use this object type.
  • Redirect
    Represents an HTTP redirect response.
  • Streamable
    Interface that identifies that an object can be serialized directly to an output stream.
  • Writable
    Interface that identifies that an object can be serialized directly to a writer.
  • ZipFileList
    Special interface for sending zip files as responses.

Additional "special types" can be defined through the ResponseHandler interface (described later).

Method Parameters

The method can contain any of the following parameters in any order:

// Example GET request using annotated attributes @RestMethod(name=GET, path="/example1/{a1}/{a2}/{a3}/*") public String doGetExample1( RestRequest req, RestResponse res, @Method String method, @Path String a1, @Path int a2, @Path UUID a3, @Query("p1") int p1, @Query("p2") String p2, @Query("p3") UUID p3, @HasQuery("p3") boolean hasP3, @PathRemainder String remainder, @Header("Accept-Language") String lang, @Header("Accept") String accept, @Header("DNT") int doNotTrack, @Properties ObjectMap properties, @Messages ResourceBundle nls ) { // Do something with all of those }

All the annotated parameters (with the exception of @Body) can be any POJO type convertible from a String. (See POJOs Convertible From String)

For example, headers can be accessed as Strings or UUIDs...

// Example GET with access to HTTP headers @RestMethod(name=GET, path="/*") public String doGet(@Header("Accept-Language") String lang, @Header("ETag") UUID eTag) throws Exception { ... }

All annotations have programmatic equivalents on the RestRequest class.

4.1.1 - Path

The @RestMethod.path() annotation allows you to define URL path patterns to match against. These patterns can contain variables of the form "{xxx}" that can be passed in directly to the Java methods as extra parameters.

In the following example, 3 separate GET request handlers are defined with different path patterns. Note how the variables are passed in as additional arguments on the method, and how those arguments are automatically converted to the specified class type...

// Default method @RestMethod(name=GET, path="/*") public void doGetDefault() { ... } // Method with path pattern @RestMethod(name=GET, path="/xxx") public void doGetNoArgs(...) { ... } // Method with path pattern with arguments @RestMethod(name=GET, path="/xxx/{foo}/{bar}/{baz}/{bing}") public void doGetWithArgs(@Path String foo, @Path int bar, @Path MyEnum baz, @Path UUID bing) { ... }

By default, path patterns are matched using a best-match heuristic. When overlaps occur, URLs are matched from most-specific to most-general order:

// Try first @RestMethod(name=GET, path="/foo/bar") public void method1() { ... } // Try second @RestMethod(name=GET, path="/foo/{bar}") public void method2(...) { ... } // Try third @RestMethod(name=GET, path="/foo/*") public void method3(...) { ... } // Try last @RestMethod(name=GET, path="/*") public void method4(...) { ... }

The match heuristic behavior can be overridden by the @RestMethod.priority() annotation property. However, in practice this is almost never needed.

Paths that end with "/*" will do a prefix match on the incoming URL. Any remainder after the match can be accessed through RequestPathMatch.getRemainder() or parameters with the @PathRemainder annotation. On the other hand, paths that don't end with "/*" (e.g. "/" or "/foo") will require an exact URL match, and if any remainder exists, a 404 (not found) error will be thrown.

The following example shows the distinction.

@RestMethod(name=GET, path="/*") public void doGet(@PathRemainder String remainder) { // URL path pattern can have remainder accessible through req.getRemainder(). } @RestMethod(name=PUT, path="/") public void doPut() { // URL path pattern must match exactly and will cause a 404 error if a remainder exists. }

Annotations are provided for easy access to URL parameters with automatic conversion to any parsable object type. For example, the following example can process the URL "/urlWithParams?foo=foo&bar=[1,2,3]&baz=067e6162-3b6f-4ae2-a171-2470b63dff00"...

// Example GET request with access to query parameters @RestMethod(name=GET, path="/urlWithParams") public String doGetWithParams(@Query("foo") String foo, @Query("bar") int bar, @Query("baz") UUID baz) throws Exception { return "GET /urlWithParams?foo="+foo+"&bar="+bar+"&baz="+baz); }

4.1.2 - Matchers

RestMatchers are used to allow multiple Java methods to be tied to the same HTTP method and path, but differentiated by some request attribute such as a specific header value.

// GET method that gets invoked for administrators @RestMethod(name=GET, path="/*", matchers=IsAdminMatcher.class) public Object doGetForAdmin() { ... } // GET method that gets invoked for everyone else @RestMethod(name=GET, path="/*") public Object doGetForEveryoneElse() { ... }

The interface for matchers is simple:

public class IsAdminMatcher extends RestMatcher { @Override public boolean matches(RestRequest req) { return req.isUserInRole("ADMINS_GROUP"); } }

Other Notes
  • If no methods are found with a matching matcher, a 412 Precondition Failed status is returned.
  • If multiple matchers are specified on the same method, ONLY ONE matcher needs to match for the method to be invoked.
  • Note that you CANNOT define identical paths on different methods UNLESS you use matchers.
    That includes paths that are only different in variable names (e.g. "/foo/{bar}" and "/foo/{baz}").
    If you try to do so, a ServletException will be thrown on startup.
  • Methods with matchers take precedence over methods without.
    Otherwise, methods are attempted in the order they appear in the class.

4.2 - Request Content

Annotations are provided for easy access to HTTP body content as any parsable POJO type (See POJO Categories). In the example below, we're POSTing beans.

// Example POST of a bean @RestMethod(name=POST, path="/") public void doPost(@Body Person person) throws Exception { // Do something with person. }

The HTTP body of a request can be retrieved as a parsed POJO using either the RestRequest.getBody() method, or a parameter annotated with @Body.

// Equivalent method 1 @RestMethod(name=POST, path="/example1") public void doPost1(@Body Person p) { // Do something with p. } // Equivalent method 2 @RestMethod(name=POST, path="/example2") public void doPost2(RestRequest req) { Person p = req.getBody).asType(Person.class); // Do something with p. }

The Juneau framework will automatically determine the appropriate Parser to use based on the Content-Type HTTP header. So the body content could be JSON or XML or any other supported parsing types.

4.2.1 - Form Posts

URL-Encoded form posts require their own topic since they can be handled in multiple ways.

The best way to handle a form post is by using an input bean. The samples include a UrlEncodedFormResource class that takes in URL-Encoded form post of the form "aString=foo&aNumber=123&aDate=2001-07-04T15:30:45Z". The code is shown here:

@RestResource( path="/urlEncodedForm" ) public class UrlEncodedFormResource extends Resource { /** POST request handler */ @RestMethod(name=POST, path="/") public Object doPost(@Body FormInputBean input) throws Exception { // Just mirror back the request return input; } public static class FormInputBean { public String aString; public int aNumber; @BeanProperty(pojoSwaps=CalendarSwap.ISO8601DT.class) public Calendar aDate; } }

Another possibility is to access the form parameters individually:

/** POST request handler */ @RestMethod(name=POST, path="/") public Object doPost(@FormData("aString") String aString, @FormData("aNumber") int aNumber, @FormData("aDate") Calendar aDate) throws Exception { ... }

The advantage to the form input bean is that it can handle any of the parsable types (e.g. JSON, XML...) in addition to URL-Encoding. The latter approach only supports URL-Encoding.

  • If you're using form input beans, DO NOT use the @FormData attribute or ServletRequestWrapper.getParameter(String) method since this will cause the underlying JEE servlet to parse the HTTP body as a form post.
    Your input bean will end up being null since there won't be any content left after the servlet has parsed the body of the request.
    This applies to WHENEVER you use @Body or RestRequest.getBody()

4.2.2 - Multipart Form Posts

The Juneau framework does not natively support multipart form posts. However, it can be used in conjunction with the Apache Commons File Upload library to do so.

The samples include a TempDirResource class that uses the File Upload library to allow files to be uploaded as multipart form posts.

@RestResource( path="/tempDir" ) public class TempDirResource extends DirectoryResource { /** * [POST /upload] - Upload a file as a multipart form post. * Shows how to use the Apache Commons ServletFileUpload class for handling multi-part form posts. */ @RestMethod(name=POST, path="/upload", matchers=TempDirResource.MultipartFormDataMatcher.class) public Redirect uploadFile(RestRequest req) throws Exception { ServletFileUpload upload = new ServletFileUpload(); FileItemIterator iter = upload.getItemIterator(req); while (iter.hasNext()) { FileItemStream item = iter.next(); if (item.getFieldName().equals("contents")) { File f = new File(getRootDir(), item.getName()); IOPipe.create(item.openStream(), new FileOutputStream(f)).closeOut().run(); } } return new Redirect(); // Redirect to the servlet root. } /** Causes a 404 if POST isn't multipart/form-data */ public static class MultipartFormDataMatcher extends RestMatcher { @Override /* RestMatcher */ public boolean matches(RestRequest req) { String contentType = req.getContentType(); return contentType != null && contentType.startsWith("multipart/form-data"); } }

4.3 - Response Content

REST Java methods can generate output in any of the following ways:

// Equivalent method 1 @RestMethod(name=GET, path="/example1/{personId}") public Person doGet1(@Path UUID personId) { Person p = getPersonById(personId); return p; } // Equivalent method 2 @RestMethod(name=GET, path="/example2/{personId}") public void doGet2(RestResponse res, @Path UUID personId) { Person p = getPersonById(personId); res.setOutput(p); } // (Sorta) Equivalent method 3 // (Ignores any converters or method-level properties) @RestMethod(name=GET, path="/example3/{personId}") public void doGet3(RestRequest req, RestResponse res, @Path UUID personId) { Person p = getPersonById(personId); String accept = req.getHeader("Accept", "text/json"); WriterSerializer s = res.getSerializerGroup().getWriterSerializer(accept); res.setContentType(s.getResponseContentType()); s.serialize(p, res.getNegotiatedWriter()); }

4.4 - Lifecycle Hooks

Lifecycle hooks allow you to hook into lifecycle events of the servlet or REST call. Like @RestMethod methods, the list of parameters are specified by the developer.

For example, if you want to add an initialization method to your resource:

@RestResource(...) public class MyResource { // Our database. private Map<Integer,Object> myDatabase; @RestHook(INIT) public void initMyDatabase(RestConfig config) throws Exception { myDatabase = new LinkedHashMap<>(); } }

Or if you want to intercept REST calls:

@RestResource(...) public class MyResource { // Add a request attribute to all incoming requests. @RestHook(PRE_CALL) public void onPreCall(RestRequest req) { req.setAttribute("foo", "bar"); } }

The hook events can be broken down into two categories:

  • Resource lifecycle events:
  • REST call lifecycle events:
    • START_CALL - At the beginning of a REST call.
    • PRE_CALL - Right before the @RestMethod method is invoked.
    • POST_CALL - Right after the @RestMethod method is invoked.
    • END_CALL - At the end of the REST call after the response has been flushed.
Additional Information

4.5 - OPTIONS Pages

One of the most useful features of Juneau is that it can produce OPTIONS pages for self-documenting designs (i.e. REST interfaces that document themselves).

OPTIONS page for HelloWorld sample resource

This page is constructed through reflection on the servlet class and it's methods, combined with information provided in the following locations:

Swagger JSON files are specified with the same name as the servlet (e.g. MyResource.java -> MyResource.json). Localized versions of Swagger JSON files can be specified by appending the locale to the file name (e.g. MyResource_ja_JP.json). The existence of Swagger JSON files will override any auto-generation of the OPTIONS pages. This allows you to fully control the contents of the OPTIONS page with your own Swagger spec.

The framework takes the information above and populates a Swagger bean. This bean is then serialized just like any other POJO to produce the page shown above.

RestServletDefault provides a default OPTIONS page by implementing a RestServletDefault.getOptions(RestRequest) method that returns a POJO consisting of beans describing the class. It uses the RestRequest.getSwagger() method that returns a localized swagger bean.

/** * [OPTIONS /*] - Show resource options. * * @param req The HTTP request. * @return A bean containing the contents for the OPTIONS page. */ @RestMethod(name=OPTIONS, path="/*", summary="Resource options", htmldoc=@HtmlDoc( navlinks={ "back: servlet:/,", "json: servlet:/?method=OPTIONS&Accept=text/json&plainText=true" } ) ) public Swagger getOptions(RestRequest req) { return req.getSwagger(); }

Title and Description

The title and description can be defined in the following ways.

If you don't care about internationalization, then the easiest way is to use annotations on the servlet.

@RestResource( path="/example", title="Example Resource", description="This shows how to use labels and descriptions." ) public class ExampleResource extends RestServletDefault {

The second approach which supports internationalization is to use the @RestResource.messages() annotation to point to a resource bundle, and then use predefined properties that identify the label and description.

@RestResource( messages="nls/Messages" ) public class ExampleResource extends RestServletDefault {

The title and description are specified as special properties in the resource bundle:

#-------------------------------------------------------------------------------- # Contents of Messages.properties #-------------------------------------------------------------------------------- title = Example Resource description = This shows how to use labels and descriptions.

Message keys can optionally be prefixed by the short class name if the resource bundle is shared by multiple servlets:

#-------------------------------------------------------------------------------- # Contents of Messages.properties #-------------------------------------------------------------------------------- ExampleResource.title = Example Resource ExampleResource.description = This shows how to use labels and descriptions.

When both annotations and properties are used, annotations take precedence.

The localized label and description are also available through the following methods:

They are also made available as the request string variables "$R{servletTitle}" and "$R{servletDescription}". These variable facilitate the localized label and descriptions on the HTML pages when using RestServletDefault:

@RestResource( pageTitle="The title for this page is $R{servletTitle}", pageText="The description for this page is $R{servletDescription}" ) public abstract class RestServletDefault extends RestServlet {

The title and description annotations support string variables. So in theory, you could also provide localized messages using "$L" variables pointing to your own resource bundle properties:

@RestResource( path="/example", messages="nls/Messages" title="$L{my.resource.label}", description="$L{my.resource.description}" ) public class ExampleResource extends RestServletDefault {

Another option is to override the RestInfoProvider.getTitle(RestRequest) and RestInfoProvider.getDescription(RestRequest) methods.

Method Description, Input, and Responses

The methods field in the OPTIONS page is mostly populated through reflection. However, the description, input, and responses field can be specified through either annotations or resource properties.

For example, the AddressBookResource has a getPerson() method that gets rendered in the OPTIONS page like so...

This method is described through the @RestMethod.description(), @RestMethod.swagger(), and @MethodSwagger annotations.

@RestMethod( name=GET, path="/people/{id}/*", converters={Traversable.class,Queryable.class,Introspectable.class}, description="Get a person by id in the address book", parameters={ @Parameter(in="path", name="id", description="Person UUID") }, responses={ @Response(value=200, description="Person bean"), @Response(value=404, description="Person with specified id not found") } ) public Person getPerson(@Path int id) throws Exception { return findPerson(id); }

These labels can also be localized by instead specifying them in the servlet properties file:

@RestMethod( name=GET, path="/people/{id}/*", converters={Traversable.class,Queryable.class,Introspectable.class} // Don't specify annotations for labels...they'll be detected in resource bundle. ) public Person getPerson(@Path int id) throws Exception { return findPerson(id); }

#-------------------------------------------------------------------------------- # Contents of AddressBookResource.properties #-------------------------------------------------------------------------------- getPerson.summary = Get a person by id in the address book getPerson.req.path.id.description = Person UUID getPerson.res.200.description = Person found getPerson.res.404.description = Person with specified id not found

The following table shows the predefined resource bundle message property names:

Property Description Equivalent Annotation Equivalent Method
label Servlet label @RestResource.title() RestInfoProvider.getTitle(RestRequest)
description Servlet description @RestResource.description() RestInfoProvider.getDescription(RestRequest)
[javaMethodName].summary Java method summary @RestMethod.summary()
[javaMethodName].description Java method description @RestMethod.description()
[javaMethodName].req.body.description A description of the HTTP request body. @MethodSwagger.parameters()
[javaMethodName].req.[category].[name].description A request input variable.
Categories: path, query, formData, header
@MethodSwagger.parameters()
[javaMethodName].res.[code].description A possible HTTP response code and description. @MethodSwagger.responses()
[javaMethodName].res.[code].body.description A description of response content for the specified HTTP response. @MethodSwagger.responses()
[javaMethodName].res.[code].header.[name].description A response header. @MethodSwagger.responses()
Additional Information

4.6 - Serializers

REST servlets use the Serializer API for defining serializers for serializing response POJOs.

The servlet will pick which serializer to use by matching the request Accept header with the media types defined through the Serializer.getMediaTypes() method.

Serializers can be associated with REST servlets in the following ways:

The following are equivalent ways of defining serializers used by a servlet...

// Example #1 - Serializers defined on servlet through annotation @RestResource( serializers={JsonSerializer.class, XmlSerializer.class} ) public MyRestServlet extends RestServlet { ... } // Example #2 - Serializers defined on method through annotation @RestMethod(name=GET, path="/*" serializers={JsonSerializer.class, XmlSerializer.class} ) public Object doGet() { ... } // Example #3 - Serializers defined on servlet by overriding the createSerializers(ObjectMap,Class[],Class[]) method @Override public SerializerGroupBuilder createSerializers(ObjectMap,Class[],Class[]) { return SerializerGroup.create() .append(JsonSerializer.class, XmlSerializer.class); }

  • When debugging the output from REST servlets, it's almost always easier to bypass the REST servlet and try to serialize the POJOs using the serializers directly using the WriterSerializer.toString(Object) method.
Additional Information

4.7 - Parsers

REST servlets use the Parser API for defining parsers for parsing request body content and converting them into POJOs.

The servlet will pick which parser to use by matching the request Content-Type header with the media types defined through the Parser.getMediaTypes() method.

Parsers can be associated with REST servlets in the following ways:

The following are equivalent ways of defining parsers used by a servlet...

// Example #1 - Parsers defined on servlet through annotation @RestResource( parsers={JsonParser.class, XmlParser.class} ) public MyRestServlet extends RestServlet { ... } // Example #2 - Parsers defined on method through annotation @RestMethod(name=GET, path="/*" parsers={JsonParser.class, XmlParser.class} ) public void doPut(@Body Foo input) { ... } // Example #3 - Parsers defined on servlet by overriding the getParserGroup method @Override public ParserGroupBuilder getParserGroup() { return ParserGroup.create() .append(JsonParser.class, XmlParser.class); }

Additional Information

4.8 - Properties

The Juneau serializers and parsers are highly-configurable through properties. (See Configurable Properties)

There are several ways of defining properties in the REST API. The most common way is going to be through the @RestResource.properties() and @RestMethod.properties() annotations.

The @RestResource.properties() annotation can be used as a convenient way to set various serializer and parser properties to all serializers and parsers registered with the servlet.

import static org.apache.juneau.SerializerContext.*; import static org.apache.juneau.xml.XmlSerializerContext.*; import static org.apache.juneau.rest.serializers.HtmlSerializerContext.*; // Servlet with properties applied @RestResource( properties={ // Nulls should not be serialized @Property(name=TRIM_NULLS, value="true"), // Empty lists should not be serialized @Property(name=SERIALIZER_trimEmptyLists, value="true"), // Specify the default namespaces for the XML serializer @Property(name=XML_defaultNamespaceUriS, value="{jp06:'http://jazz.net/xmlns/prod/jazz/process/0.6/',jp:'http://jazz.net/xmlns/prod/jazz/process/1.0/'}"), // Specify a default title for the HtmlSerializer serializer // This is equivalent to @RestResource(title/pageTitle) @Property(name=HTMLDOC_title, value="My resource") } ) public MyRestServlet extends RestServlet { ... }

The @RestMethod.properties() annotation can be used to define method-level properties that can alter the behavior of serializers and parsers at the method level only.

// GET method with method-level properties @RestMethod( name=GET, path="/*", properties={ // Nulls should not be serialized @Property(name=TRIM_NULLS, value="true"), // Empty lists should not be serialized @Property(name=SERIALIZER_trimEmptyLists, value="true"), // Specify the default namespaces for the XML serializer @Property(name=XML_defaultNamespaceUriS, value="{jp06:'http://jazz.net/xmlns/prod/jazz/process/0.6/',jp:'http://jazz.net/xmlns/prod/jazz/process/1.0/'}"), // Specify a default title for the HtmlSerializer serializer // This is equivalent to @RestMethod(pageTitle) @Property(name=HTMLDOC_title, value="My resource") } public Object doGet() { ... }

In particular, the RestContext class has a variety of properties for controlling the behavior of the RestServlet class itself.

There are also ways to provide properties programmatically.

Additional Information

4.9 - Transforms

The Juneau serializers and parsers can be configured on how to handle POJOs through the use of Transforms. (See Transforms)

The @RestResource.beanFilters() and @RestResource.pojoSwaps() annotations can be used as a convenient way to add bean filters and POJO swaps to the serializers and parsers registered with the servlet.

// Servlet with transforms applied @RestResource( pojoSwaps={ // Calendars should be serialized/parsed as ISO8601 date-time strings CalendarSwap.DEFAULT_ISO8601DT.class, // Byte arrays should be serialized/parsed as BASE64-encoded strings ByteArrayBase64Swap.class, }, beanFilters={ // Subclasses of MyInterface will be treated as MyInterface objects. // Bean properties not defined on that interface will be ignored. MyInterface.class } ) public MyRestServlet extends RestServlet { ... }

@RestMethod.beanFilters() and @RestMethod.pojoSwaps() are the equivalent annotations for individual Java methods.

Transforms can also be defined programmatically through the following:

Additional Information

4.10 - Guards

Guards are classes that control access to REST servlets and methods.

The @RestResource.guards() annotation can be used to associate one or more class-level RestGuards with a servlet.

// Servlet with class-level guard applied @RestResource(guards=BillyGuard.class) public MyRestServlet extends RestServlet { // Delete method that only Billy is allowed to call. public doDelete(RestRequest req, RestResponse res) throws Exception {...} } // Define a guard that only lets Billy make a request public BillyGuard extends RestGuard { @Override public boolean isRequestAllowed(RestRequest req) { return req.getUserPrincipal().getName().equals("Billy"); } }

A common use for guards is to only allow admin access to certain Java methods...

// DELETE method @RestMethod(name=DELETE, guards={AdminGuard.class}) public void doDelete(RestRequest req, RestResponse res) throws Exception { ...

public class AdminGuard extends RestGuard { @Override public boolean isRequestAllowed(RestRequest req) { return req.getUserPrincipal().isUserInRole("ADMIN"); } }

A guard failure results in an HTTP 401 Unauthorized response. However, this can be configured by overriding the RestGuard.guard(RestRequest,RestResponse) and processing the response yourself.

When guards are associated at the class-level, it's equivalent to associating guards on all Java methods on the servlet.

Class-level guards can also be created programmatically by overriding the RestConfig.addGuards(Class[]) method.

Additional Information

4.11 - Converters

Converters can be thought of as a "post-processor" for POJOs before they get passed to the serializers.

The @RestResource.converters() annotation can be used as a convenient way to add RestConverters to all Java REST methods on a servlet.

// Associate the Traversable converter to all Java REST methods in this servlet @RestResource(converters=Traversable.class) public MyRestServlet extends RestServlet { ... }

The @RestMethod.converters() annotation can be used to associate converters on individual methods.

// GET person request handler. // Traversable conversion enabled to allow nodes in returned POJO tree to be addressed. // Queryable conversion enabled to allow returned POJO to be searched/viewed/sorted. @RestMethod( name=GET, path="/people/{id}/*", converters={Traversable.class,Queryable.class} ) public Person getPerson(@Path int id) { return findPerson(id); }

The following converter is used to provide support for addressing child nodes in a POJO tree with URL path remainders.
In this code, the 3rd parameter is the object that was returned by the Java method (or set through request.setObject(o);).
The converter uses the PojoRest wrapper class to address nodes in the tree.

/** * Converter for enablement of PojoRest support on response objects returned by a @RestMethod method. * When enabled, objects in a POJO tree returned by the REST method can be addressed through additional URL path information. */ public class Traversable implements RestConverter { @Override public Object convert(RestServlet resource, RestRequest req, Object o) { if (o == null) return null; BeanContext beanContext = resource.getBeanContext(); if (req.getRemainder() != null) { PojoRest p = new PojoRest(o, beanContext); try { o = p.get(req.getRemainder()); } catch (PojoRestException e) { throw new RestException(e.getStatus(), e.getMessage(), e); } } return o; } }

Juneau defines the following converters out-of-the-box:

  • RestConverter
    • Queryable
      Provides query parameters that can be used to transform the response (i.e. search/view/sort the POJO response before being serialized).
    • Traversable
      Allows nodes in the POJO response tree to be individually accessed through additional path info on the request.
    • Introspectable
      Allows method calls to be made on the response POJO, and for the result of that method call to be serialized as the response.

Class-level converters can be created programmatically by overriding the RestConfig.addConverters(Class[]) method.

Note that from the example above, you can specify more than one converter. When multiple converters are used, they're executed in the order they're specified in the annotation (e.g. first the results will be traversed, then the resulting node will be searched/sorted).

Additional Information

4.12 - Child Resources

Child Resources are REST servlets that are linked to parent servlets through the @RestResource.children() annotation.

/** Parent Resource */ @RestResource( path="/parent", children={Foo.class} ) public MyResource extends RestServlet { ...

/** Child Resource */ @RestResource( path="/foo" // Path relative to parent resource. ) public FooResource extends RestServlet { ...

A HUGE advantage of using child resources is that they do not need to be declared in the JEE web.xml file. Initialization of and access to the child resources occurs through the parent resource. Children can be nested arbitrary deep to create complex REST interfaces with a single top-level REST servlet.

The path of the child resource gets appended to the path of the parent resource. So in the example above, the child resource is accessed through the URL /parent/foo.

The RestServletGroupDefault class provides a default "router" page for child resources when a parent resource is nothing more than a grouping of child resources.

The RootResources class in the Samples project is an example of a router page:

/** * Sample REST resource showing how to implement a "router" resource page. */ @RestResource( path="/", children={ HelloWorldResource.class, MethodExampleResource.class, RequestEchoResource.class, TempDirResource.class, AddressBookResource.class, SampleRemoteableServlet.class, PhotosResource.class, AtomFeedResource.class, JsonSchemaResource.class, SqlQueryResource.class, TumblrParserResource.class, CodeFormatterResource.class, UrlEncodedFormResource.class, SourceResource.class, ConfigResource.class, LogsResource.class, DockerRegistryResource.class, ShutdownResource.class } ) public class RootResources extends ResourceGroup { private static final long serialVersionUID = 1L; }

When you bring up this resource in a browser, you see the following:

The RestServletGroupDefault class is nothing more than a subclass of RestServletDefault with a getChildren() method mapped to the servlet root path. The method returns a POJO with is just a linked-list of beans with name/description properties.

public class RestServletGroupDefault extends RestServletDefault { /** * [GET /] - Get child resources. * * @param req The HTTP request. * @return The bean containing links to the child resources. */ @RestMethod(name=GET, path="/", description="Child resources") public ChildResourceDescriptions getChildren(RestRequest req) { return new ChildResourceDescriptions(this, req); } }

Children can also be defined programmatically by overriding any of the following methods:

4.13 - Localized Messages

The @RestResource.messages() annotation can be used to associate a resource bundle with a servlet class.

// Servlet with associated resource bundle @RestResource(messages="nls/MyMessages") public MyRestServlet extends RestServlet { // Returns the localized greeting from the "greeting" key in MyMessages.properties @RestMethod(name=GET, path="/") public String printLocalizedGreeting(RestRequest req) { return req.getMessage("greeting"); }

The resource bundle can also be passed into the method by using the @Messages annotation:

@RestMethod(name=GET) public String printLocalizedGreeting(@Messages ResourceBundle messages) { return messages.getString("greeting"); }

If a resource bundle is shared by multiple servlets, the label and description can be prefixed by the class name:

#-------------------------------------------------------------------------------- # Contents of MyMessages.properties #-------------------------------------------------------------------------------- greeting = Hello!

#-------------------------------------------------------------------------------- # Contents of shared MyMessages.properties #-------------------------------------------------------------------------------- MyRestServlet.greeting = Hello!

4.14- Encoders

The @RestResource.encoders() annotation can be used to associate character encoders with a servlet class. Encoders can be used to enable various kinds of compression (e.g. "gzip") on requests and responses based on the request Accept-Encoding and Content-Encoding headers.

Example:

// Servlet with automated support for GZIP compression @RestResource(encoders={GzipEncoder.class}) public MyRestServlet extends RestServlet { ... }

Juneau defines the following encoders out-of-the-box:

4.15 - SVL Vars

In the previous examples, there were several cases where embedded variables were contained within annotation values:

@RestResource( title="$L{my.label}" )

Variables are of the form $X{key}, where X can consist of zero or more ASCII characters. This is called Simple Variable Language, or SVL.

Features include:

  • Variables can be nested arbitrarily deep (e.g. "$X{$Y{foo}}").
  • Variables can contain arguments (e.g. "$L{my.label,arg1,arg2}").
  • Variables are recursively resolved.
    i.e., if a variable results to a value with another variable in it, that variable will also be resolved.

String variables are implemented through the VarResolver class and one or more Vars.

Example:

// Create a variable that resolves system properties (e.g. "$S{java.home}") public class SystemPropertiesVar extends SimpleVar { public SystemPropertiesVar() { super("S"); } @Override public String resolve(VarResolverSession session ,String varVal) { return System.getProperty(varVal); } } // Create a resolver that used that variable VarResolver r = new VarResolver().addVars(SystemPropertiesVar.class); // Use that resolver to resolve a variable in a string System.out.println(r.resolve("java.home is set to $S{java.home}"));

The methods that define the var resolver on a servlet are:

The default RestContext.getVarResolver() method provides support the following string variable types:

Variable Description
$S{key}
$S{key,default}
System properties.
$E{key}
$E{key,default}
Environment variables.
$I{key}
$I{key,default}
Servlet initialization parameters.
$C{key}
$C{key,default}
Values from the config file returned by RestContext.getConfigFile().

The default values are used when a property doesn't resolve to a value.
(e.g. "$S{myBooleanProperty,true}").

Like all other variables, keys and default values can themselves be arbitrarily nested.
(e.g. "$S{$E{BOOLEAN_PROPERTY_NAME},$E{BOOLEAN_DEFAULT}}")

Subclasses can augment this list by adding their own variables.

Example:

@Override /* RestServlet */ protected VarResolver createVarResolver() { // Augment the default variable resolver. return super.createVarResolver().addVars(BracketVar.class); r.addVar("BRACKET", new StringVar() { }); return s; } public static class BracketVar extends SimpleVar { public BracketVar() { super("BRACKET"); } // Wrap all strings inside [] brackets. // e.g. "$BRACKET{foobar}" -> "[foobar]" @Override public String resolve(VarResolverSession session, String varVal) { return '[' + varVal + ']'; } }

The default RestRequest.getVarResolverSession() method provides support for all the servlet-level variables, and augments it with the following request-specific variable types:

Variable Description
$L{key}
$L{key,args...}
Localized strings pulled from resource bundle.
Resolves to the value returned by RestRequest.getMessage(String, Object...).
Can include message arguments (e.g. "$L{my.localized.string, foo, bar}")
$R{key} Request-specific variables.
Possible values:
$UE{...} URL-Encode the specified value.
Takes the contents inside the variable and replaces it with a URL-encoded string.

In addition to being used in annotation values, string variables can also be embedded in resource files retrieved through the RestRequest.getReaderResource(String,boolean) method. This can often be useful for embedding localized strings inside HTML form pages.

The UrlEncodedFormResource class in the Samples shows an example of using an HTML form page with localized variables. When you bring it up in a browser, you see the following:

This HTML page is a static file located in the org.apache.juneau.rest.samples package.

Contents of org/apache/juneau/server/samples/UrlEncodedForm.html
TODO - Needs update

<html> <head> <style type='text/css'> @import '$R{servletURI}/style.css'; </style> </head> <body> <h1>$R{servletTitle}</h1> <h2>$R{servletDescription}</h2> <div class='data'> <form id='form' action='$R{servletURI}' method='POST'> <table> <tr> <th>$L{aString}</th> <td><input name="aString" type="text"></td> </tr> <tr> <th>$L{aNumber}</th> <td><input name="aNumber" type="number"></td> </tr> <tr> <th>$L{aDate}</th> <td><input name="aDate" type="datetime"> (ISO8601, e.g. "<code>2001-07-04T15:30:45Z</code>")</td> </tr> <tr> <td colspan='2' align='right'><button type="submit">$L{submit}</button></td> </tr> </table> </form> </div> </body> </html>

Contents of UrlEncodedFormResource.java

/** * Sample REST resource for loading URL-Encoded form posts into POJOs. */ @RestResource( path="/urlEncodedForm", messages="nls/UrlEncodedFormResource", title="URL-encoded Form Post Resource", description="Shows how form posts are converted into beans.", htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS" } ) ) public class UrlEncodedFormResource extends Resource { private static final long serialVersionUID = 1L; /** GET request handler */ @RestMethod(name=GET, path="/") public ReaderResource doGet(RestRequest req) throws IOException { return req.getReaderResource("UrlEncodedForm.html", true); } /** POST request handler */ @RestMethod(name=POST, path="/") public Object doPost(@Body FormInputBean input) throws Exception { // Just mirror back the request return input; } public static class FormInputBean { public String aString; public int aNumber; @BeanProperty(pojoSwaps=CalendarSwap.ISO8601DT.class) public Calendar aDate; } }

Contents of UrlEncodedFormResource.properties

#-------------------------------------------------------------------------------- # UrlEncodedFormResource labels #-------------------------------------------------------------------------------- label = URL-Encoded Form Post Example description = Shows how URL-Encoded form input can be loaded into POJOs. POJO is simply echoed back. aString = A String: aNumber = A Number: aDate = A Date: submit = submit

Additional Information

4.16 - Static Files

The @RestResource.staticFiles() annotation is used to define paths and locations of statically-served files such as images or HTML documents.

The value is a JSON map of paths to packages/directories located on either the classpath or working directory.

Example:

package com.foo.mypackage; @RestResource( path="/myresource", staticFiles="{htdocs:'docs'}" ) public class MyResource extends RestServletDefault { }

Static files are found by calling Class.getResource(String) up the class hierarchy. If not found, then an attempt is made to find the class in the Java working directory.

In the example above, given a GET request to /myresource/htdocs/foobar.html, the servlet will attempt to find the foobar.html file in the following ordered locations:

  1. com.foo.mypackage.docs package.
  2. org.apache.juneau.rest.docs package (since RestServletDefault is in org.apache.juneau.rest).
  3. [working-dir]/docs directory.
Notes:
  • Mappings are cumulative from parent to child. Child resources can override mappings made on parent resources.
  • The media type on the response is determined by the RestContext.getMediaTypeForName(String) method.

4.17 - Listener Methods

Various convenience listener methods can be implemented by using the @RestHook annotation on methods in your resource class.

4.18 - Stylesheet

TODO

4.19 - Default Headers

The following annotations are provided for specifying default header values for requests and responses:

Example:

// Servlet with default headers @RestResource( // Assume "text/json" Accept value when Accept not specified defaultRequestHeaders={"Accept: text/json"}, // Add a version header attribute to all responses defaultResponseHeaders={"X-Version: 1.0"} ) public MyRestServlet extends RestServlet { ... }

Default headers can also be specified programmatically by overriding the following methods:

4.20 - Handling Errors / Logging

The following overridable methods are provided for handling errors on requests:

The following convenience methods are provided for logging:

The logObjects() method is particularly useful because it allows you to pass in POJOs as arguments that serialized using JsonSerializer.DEFAULT_LAX_READABLE, but only if the message is actually logged.

Example:

logObjects(DEBUG, "Pojo contents:\n{0}", myPojo);

The Juneau framework uses the built-in Java Logging API for logging.

If your application makes use of Apache Commons Logging or some other logging API, you can override the RestServlet.log(Level,String,Object[]) method to provide a bridge between the two frameworks.

@Override /* RestServlet */ protected void log(Level level, Throwable cause, String msg, Object...args) { Log log = getApacheCommonsLog(); if (level == Level.SEVERE) log.error(msg, cause, args); else if (level == Level.WARNING) log.warn(msg, cause, args); else if (level == Level.INFO) log.info(msg, cause, args); else log.debug(msg, cause, args); }

4.21 - Configuration Files

The Juneau Configuration API is an entirely separate topic from the REST support. But the Server API provides methods for associating configuration files with REST servlets so that configuration properties can be defined in external files.

The Configuration API provides support for INI-style configuration files with embedded string variables:

Example:

#-------------------------- # Examples #-------------------------- [MyProperties] path = $E{PATH} javaHome = $S{java.home} customMessage = Java home is $C{MyProperties/javaHome} and the environment path is $C{MyProperties/path}.

These properties are then accessible through the ConfigFile class.

ConfigFile cf = ConfigFile.create().build("myconfig.cfg"); String path = cf.getString("MyProperties/path"); File javaHome = cf.getObject(File.class, "MyProperties/javaHome"); String customMessage = cf.getString("MyProperties/customMessage");

Configuration files are associated with REST servlets through the @RestResource.config() annotation.

Example:

@RestResource( // Config file is located at ./config_dir/myconfig.cfg config="config_dir/myconfig.cfg", ... ) public MyRestServlet extends RestServlet {

The annotation itself can contain string variables. For example, the Microservice API Resource class defines the location of the config file as a system property "juneau.configFile":

@RestResource( // Config file location is defined as a system property config="$S{juneau.configFile}", ... ) public abstract class Resource extends RestServletDefault {

In that particular case, the system property can either be passed in, or be determined programmatically based on the jar file name in the Microservice class. It should be noted that the Configuration API is used extensively in the Microservice API in order to externally configure microservices.

Once a config file has been associated with a REST servlet, it can be accessed through the RestContext.getConfigFile() method.

A common usage is to use this method to initialize fields in your servlet.

@RestResource( // Config file is located at ./config_dir/myconfig.cfg config="config_dir/myconfig.cfg", ... ) public MyRestServlet extends RestServlet { private String path = getConfig().getString("MyProperties/path"); private File javaHome = getConfig().getObject(File.class, "MyProperties/javaHome");

Another common usage is to refer to config properties through $C variables in your annotations:

@RestResource( // Get stylesheet from myconfig.cfg, but default to devops.css if it's not specified htmldoc=@HtmlDoc( stylesheet="$C{MyServlet/stylesheet,servlet:/styles/devops.css}", ) ... ) public MyRestServlet extends RestServlet {

It's even possible to reference request-level variables in your config file if you use RestRequest.getConfigFile() to access the config file:

#------------------------------------- # Contents of config_dir/myconfig.cfg #------------------------------------- [HelloWorldResource] defaultPerson = you message = Hello $R{query.person,$C{HelloWorldResource/defaultPerson}}!

/** * Sample REST resource that prints out a simple "Hello world!" message. */ @RestResource( config="config_dir/myconfig.cfg", ... ) public class HelloWorldResource extends RestServletDefault { /** * GET request handler. * Specify the GET parameter "?person=X" for a specialized message! */ @RestMethod(name=GET, path="/") public String sayHello(RestRequest req) { return req.getConfig().getString("HelloWorldResource/message"); } }

You can even add resource bundles into the mix:

#------------------------------------- # Contents of config_dir/myconfig.cfg #------------------------------------- [HelloWorldResource] defaultPerson = you message = $L{localizedMessage,$R{query.person,$C{HelloWorldResource/defaultPerson}}}

#------------------------------------------- # Contents of HelloWorldResource.properties #------------------------------------------- localizedMessage = Hello {0}!

/** * Sample REST resource that prints out a simple "Hello world!" message. */ @RestResource( messages="HelloWorldResources", config="config_dir/myconfig.cfg", ... ) public class HelloWorldResource extends RestServletDefault { /** * GET request handler. * Specify the GET parameter "?person=X" for a specialized message! */ @RestMethod(name=GET, path="/") public String sayHello(RestRequest req) { return req.getConfig().getString("HelloWorldResource/message"); } }

Additional Information

4.22 - Annotation Inheritance

The @RestResource annotation can be used on parent classes and interfaces. When multiple annotations are defined at different levels, the annotation values are combined. Child annotation values always take precedence over parent annotation values.

Annotation Inheritence Rules
guards() Guards on child are combined with those on parent class.
Guards are executed child-to-parent in the order they appear in the annotation.
Guards on methods are executed before those on classes.
converters() Converters on child are combined with those on parent class.
Converters are executed child-to-parent in the order they appear in the annotation.
Converters on methods are executed before those on classes.
beanFilters() Bean filters on child are combined with those on parent class.
pojoSwaps() POJO swaps on child are combined with those on parent class.
properties() Properties on child are combined with those on parent class.
Properties are applied parent-to-child in the order they appear in the annotation.
Properties on methods take precedence over those on classes.
serializers() Serializers on child are combined with those on parent class.
Serializers on methods take precedence over those on classes.
parsers() Parsers on child are combined with those on parent class.
Parsers on methods take precedence over those on classes.
responseHandlers() Response handlers on child are combined with those on parent class.
encoders() Encoders on child are combined with those on parent class.
defaultRequestHeaders() Headers on child are combined with those on parent class.
Headers are applied parent-to-child in the order they appear in the annotation.
Headers on methods take precedence over those on classes.
defaultResponseHeaders() Headers on child are combined with those on parent class.
Headers are applied parent-to-child in the order they appear in the annotation.
children() Children on child are combined with those on parent class.
Children are list parent-to-child in the order they appear in the annotation.
path() Path is searched for in child-to-parent order.
title() Label is searched for in child-to-parent order.
description() Description is searched for in child-to-parent order.
config() Config file is searched for in child-to-parent order.
staticFiles() Static files on child are combined with those on parent class.
Static files are are executed child-to-parent in the order they appear in the annotation.

4.23 - HTTP Status Codes

By default, a 200 (OK) status is automatically set as the HTTP status when a Java method executes successfully.

Other status codes can be generated by throwing a RestException with a specific HTTP status code, or calling HttpServletResponse.setStatus(int).

Non-OK (200) status codes are automatically triggered by the following...

Code Description When triggered
401 Unauthorized A guard prevented the method from being executed
404 Not Found No matching path patterns were found on any method
405 Method Not Implemented A path pattern matched, but no Java methods were found for the HTTP method
406 Not Acceptable A path pattern matched, but no Java methods were found with a matching serializer for the Accept on the request
412 Precondition Failed A path pattern matched, but no Java methods were found that were not rejected by matchers
415 Unsupported Media Type A path pattern matched, but no Java methods were found with a matching parser for the Content-Type on the request
500 Internal Server Error The Java method threw an exception other than RestException

4.24 - Overloaded HTTP Methods

Through the use of the built-in "method" GET parameter, you can implement requests beyond the basic REST http method types.

For example, the URL "/sample/foo?method=BAR" will cause the following method to be invoked...

@RestMethod(name="BAR") public void doBar(RestRequest req, RestResponse res) { // Handle BAR requests }

To support overloaded methods, the RestResource.allowMethodParam() setting must be enabled on your servlet.

@RestResource( // Allow &method parameter on BAR requests allowMethodParam="BAR" )

4.25 - Built-In Parameters

The following URL parameters have special meaning and can be passed in through the URL of the request:

GET Parameter Description
&plainText=true Response will always be Content-Type: text/plain and the returned text will be human-readable (Serializer.SERIALIZER_useWhitespace enabled).
Useful for debugging.
&debug=true Request body content will be dumped to log file.
&noTrace=true If an error occurs, don't log the stack trace to the log file.
Useful for automated JUnit testcases testing error states to prevent the log file from filling up with useless stack traces.
&method=X Overload the HTTP method as a GET parameter (e.g "POST").
Must be enabled via RestResource.allowMethodParam() setting.
&X=headerValue Specify a header value as a GET parameter.
Must be enabled via RestResource.allowHeaderParams() setting.
&content=X Pass in the HTTP body content on PUT and POST methods as a UON-encoded GET parameter.
Must be enabled via RestResource.allowBodyParam() setting.

4.26 - Defining your own serializers/parsers

A very easy-to-use API is provided for defining your own serializers and parsers at both the servlet and method levels.

The following examples show a custom serializer and parser defined at the method level. It's the PhotosResource class pulled from the Samples project. It shows an example of defining a serializer and parser to handle images.

/** * Sample resource that allows images to be uploaded and retrieved. */ @RestResource( path="/photos", messages="nls/PhotosResource", title="Photo REST service", description="Use a tool like Poster to upload and retrieve jpeg and png images.", htmldoc=@HtmlDoc( navlinks={ "options: ?method=OPTIONS" } ) ) public class PhotosResource extends RestServletDefault { // Our cache of photos private Map<Integer,Photo> photos = new TreeMap<Integer,Photo>(); /** Bean class for storing photos */ public static class Photo { private int id; BufferedImage image; Photo(int id, BufferedImage image) { this.id = id; this.image = image; } public URI getURI() throws URISyntaxException { return new URI("photos/"+id); } public int getID() { return id; } } /** GET request handler for list of all photos */ @RestMethod(name=GET, path="/") public Collection<Photo> getAllPhotos(RestRequest req, RestResponse res) throws Exception { res.setPageTitle("Photo REST service"); res.setPageText("Use a tool like Poster to upload and retrieve jpeg and png images."); return photos.values(); } /** GET request handler for single photo */ @RestMethod(name=GET, path="/{id}", serializers=ImageSerializer.class) public BufferedImage getPhoto(RestRequest req, @Path int id) throws Exception { Photo p = photos.get(id); if (p == null) throw new RestException(SC_NOT_FOUND, "Photo not found"); return p.image; } /** PUT request handler */ @RestMethod(name=PUT, path="/{id}", parsers=ImageParser.class) public String addPhoto(RestRequest req, @Path int id, @Body BufferedImage image) throws Exception { photos.put(id, new Photo(id, image)); return "OK"; } /** POST request handler */ @RestMethod(name=POST, path="/", parsers=ImageParser.class) public Photo setPhoto(RestRequest req, @Body BufferedImage image) throws Exception { int id = photos.size(); Photo p = new Photo(id, image); photos.put(id, p); return p; } /** DELETE request handler */ @RestMethod(name=DELETE, path="/{id}") public String deletePhoto(RestRequest req, @Path int id) throws Exception { Photo p = photos.remove(id); if (p == null) throw new RestException(SC_NOT_FOUND, "Photo not found"); return "OK"; } /** OPTIONS request handler */ @RestMethod(name=OPTIONS, path="/*") public Swagger getOptions(RestRequest req) { return req.getSwagger(); } /** Serializer for converting images to byte streams */ @Produces("image/png,image/jpeg") public static class ImageSerializer extends OutputStreamSerializer { @Override public void serialize(Object o, OutputStream out, SerializerSession session) throws IOException, SerializeException { RenderedImage image = (RenderedImage)o; String mediaType = ctx.getMediaType(); ImageIO.write(image, mediaType.substring(mediaType.indexOf('/')+1), out); } } /** Parser for converting byte streams to images */ @Consumes("image/png,image/jpeg") public static class ImageParser extends InputStreamParser { @Override public <T> T parse(InputStream in, ClassMeta<T> type, ParserSession session) throws ParseException, IOException { BufferedImage image = ImageIO.read(in); return (T)image; } } }

4.27 - Response Handlers

Juneau uses ResponseHandlers to convert POJOS returned by REST methods to proper HTTP responses.

Juneau comes with the following response handlers out-of-the-box:

The list of response handlers can be modified or augmented in one of the following ways:

The RestCallHandler.handleResponse(RestRequest,RestResponse,Object) method can also be overridden to bypass the response handler API and process the POJO yourself.

4.28 - Other Notes

  • Subclasses can use either GenericServlet.init(ServletConfig) or GenericServlet.init() for initialization just like any other servlet.
  • The X-Response-Headers header can be used to pass through header values into the response. The value should be a URL-encoded map of key-value pairs. For example, to add a "Refresh: 1" header to the response to auto-refresh a page, the following parameter can be specified: "/sample?X-Response-Headers={Refresh=1}"

5 - Using with OSGi

Since REST servlets are basically just HttpServlets, incorporating them into an OSGi environment is pretty straightforward.

The following code shows how to register your REST servlets in an OSGi Activator:

package org.apache.juneau.examples.rest; import org.osgi.framework.*; import org.osgi.service.http.*; import org.osgi.util.tracker.*; import org.apache.juneau.rest.samples.*; /** * Activator class used when running samples as a bundle in an OSGi environment. */ public class Activator implements BundleActivator, ServiceTrackerCustomizer { private ServiceTracker httpServiceTracker; private BundleContext context; @Override public void start(BundleContext context) throws Exception { this.context = context; httpServiceTracker = new ServiceTracker(context, HttpService.class.getName(), this); httpServiceTracker.open(); } @Override public void stop(BundleContext context) throws Exception { httpServiceTracker.close(); } @Override public Object addingService(ServiceReference reference) { Object service = context.getService(reference); if (service instanceof HttpService) { HttpService s = (HttpService)service; try { s.registerServlet("/sample", new MyRestServlet(), null, null); } catch (Exception e) { throw new RuntimeException(e); } } return service; } @Override public void modifiedService(ServiceReference reference, Object service) { } @Override public void removedService(ServiceReference reference, Object service) { } }

6 - POJOs Convertible From Strings

Certain methods in the REST server API allow you to specify class types that can be convertible from Strings (e.g. TreeMap.get(Object)).

POJOs convertible from Strings have one of the following:

  • One of the following static methods (first match wins):
    • public static T fromString(String in); (e.g. enums, UUID)
    • public static T valueOf(String in); (e.g. Number)
    • public static T parse(String in); (e.g. Java logging Level)
    • public static T parseString(String in); (e.g. DatatypeConverter)
    • public static T forName(String in); (e.g. Class and Charset)
  • A constructor that takes in a single String:
    • public T(String in);
  • Has a PojoSwap associated with the servlet with a swapped type of String.

7 - Address Book Resource

The AddressBookResource class that's found in the microservice-samples-project.zip file provides a good overall example of how to use the Juneau server API with beans.

When you start the microservice and point your browser to the address book, you should see the following:

Use the built-in Accept GET parameter to simulate different language requests, such as JSON:

The source for this class is shown below:

AddressBookResource.java

/** * Proof-of-concept resource that shows off the capabilities of working with POJO resources. * Consists of an in-memory address book repository. */ @RestResource( path="/addressBook", messages="nls/AddressBookResource", // Links on the HTML rendition page. // "request:/..." URIs are relative to the request URI. // "servlet:/..." URIs are relative to the servlet URI. // "$C{...}" variables are pulled from the config file. htmldoc=@HtmlDoc( navlinks={ "up: request:/..", "options: servlet:/?method=OPTIONS", "source: $C{Source/gitHub}/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java" }, // Our stylesheet for the HTML rendition. stylesheet="servlet:/styles/devops.css", ), // Allow INIT as a method parameter. allowMethodParam="*", // Properties that get applied to all serializers and parsers. properties={ // Use single quotes. @Property(name=SERIALIZER_quoteChar, value="'"), // Make RDF/XML readable. @Property(name=RDF_rdfxml_tab, value="5"), // Make RDF parsable by adding a root node. @Property(name=RDF_addRootProperty, value="true"), // Make URIs absolute so that we can easily reference them on the client side. @Property(name=SERIALIZER_uriResolution, value="ABSOLUTE") // Make the anchor text on URLs be just the path relative to the servlet. @Property(name=HTML_uriAnchorText, value="SERVLET_RELATIVE") }, // Support GZIP encoding on Accept-Encoding header. encoders=GzipEncoder.class, // Swagger info. swagger=@ResourceSwagger( contact="{name:'John Smith',email:'john@smith.com'}", license="{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'}", version="2.0", termsOfService="You're on your own.", tags="[{name:'Java',description:'Java utility',externalDocs:{description:'Home page',url:'http://juneau.apache.org'}}]", externalDocs="{description:'Home page',url:'http://juneau.apache.org'}" ) ) public class AddressBookResource extends ResourceJena { private static final long serialVersionUID = 1L; // The in-memory address book private AddressBook addressBook; @Override /* Servlet */ public void init() { try { // Create the address book addressBook = new AddressBook(java.net.URI.create("servlet:/")); // Add some people to our address book by default addressBook.createPerson( new CreatePerson( "Barack Obama", toCalendar("Aug 4, 1961"), new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, true), new CreateAddress("5046 S Greenwood Ave", "Chicago", "IL", 60615, false) ) ); addressBook.createPerson( new CreatePerson( "George Walker Bush", toCalendar("Jul 6, 1946"), new CreateAddress("43 Prairie Chapel Rd", "Crawford", "TX", 76638, true), new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, false) ) ); } catch (Exception e) { throw new RuntimeException(e); } } /** * [GET /] * Get root page. */ @RestMethod(name=GET, path="/", converters=Queryable.class ) public Link[] getRoot() throws Exception { return new Link[] { new Link("people", "people"), new Link("addresses", "addresses") }; } /** * [GET /people/*] * Get all people in the address book. * Traversable transforming enabled to allow nodes in returned POJO tree to be addressed. * Introspectable transforming enabled to allow public methods on the returned object to be invoked. */ @RestMethod(name=GET, path="/people/*", converters={Traversable.class,Queryable.class,Introspectable.class} ) public List<Person> getAllPeople() throws Exception { return addressBook.getPeople(); } /** * [GET /people/{id}/*] * Get a single person by ID. * Traversable transforming enabled to allow nodes in returned POJO tree to be addressed. * Introspectable transforming enabled to allow public methods on the returned object to be invoked. */ @RestMethod(name=GET, path="/people/{id}/*", converters={Traversable.class,Queryable.class,Introspectable.class} ) public Person getPerson(@Path int id) throws Exception { return findPerson(id); } /** * [GET /addresses/*] * Get all addresses in the address book. */ @RestMethod(name=GET, path="/addresses/*", converters={Traversable.class,Queryable.class} ) public List<Address> getAllAddresses() throws Exception { return addressBook.getAddresses(); } /** * [GET /addresses/{id}/*] * Get a single address by ID. */ @RestMethod(name=GET, path="/addresses/{id}/*", converters={Traversable.class,Queryable.class} ) public Address getAddress(@Path int id) throws Exception { return findAddress(id); } /** * [POST /people] * Create a new Person bean. */ @RestMethod(name=POST, path="/people", guards=AdminGuard.class ) public Redirect createPerson(@Body CreatePerson cp) throws Exception { Person p = addressBook.createPerson(cp); return new Redirect("people/{0}", p.id); } /** * [POST /people/{id}/addresses] * Create a new Address bean. */ @RestMethod(name=POST, path="/people/{id}/addresses", guards=AdminGuard.class ) public Redirect createAddress(@Path int id, @Body CreateAddress ca) throws Exception { Person p = findPerson(id); Address a = p.createAddress(ca); return new Redirect("addresses/{0}", a.id); } /** * [DELETE /people/{id}] * Delete a Person bean. */ @RestMethod(name=DELETE, path="/people/{id}", guards=AdminGuard.class, ) public String deletePerson(@Path int id) throws Exception { addressBook.removePerson(id); return "DELETE successful"; } /** * [DELETE /addresses/{id}] * Delete an Address bean. */ @RestMethod(name=DELETE, path="/addresses/{id}", guards=AdminGuard.class ) public String deleteAddress(@Path int addressId) throws Exception { Person p = addressBook.findPersonWithAddress(addressId); if (p == null) throw new RestException(SC_NOT_FOUND, "Person not found"); Address a = findAddress(addressId); p.addresses.remove(a); return "DELETE successful"; } /** * [PUT /people/{id}/*] * Change property on Person bean. */ @RestMethod(name=PUT, path="/people/{id}/*", guards=AdminGuard.class ) public String updatePerson(RestRequest req, @Path int id, @PathRemainder String pathRemainder) throws Exception { try { Person p = findPerson(id); PojoRest r = new PojoRest(p); ClassMeta<?> cm = r.getClassMeta(pathRemainder); Object in = req.getBody().asType(cm); r.put(pathRemainder, in); return "PUT successful"; } catch (Exception e) { throw new RestException(SC_BAD_REQUEST, "PUT unsuccessful").initCause(e); } } /** * [PUT /addresses/{id}/*] * Change property on Address bean. */ @RestMethod(name=PUT, path="/addresses/{id}/*", guards=AdminGuard.class ) public String updateAddress(RestRequest req, @Path int id, @PathRemainder String pathRemainder) throws Exception { try { Address a = findAddress(id); PojoRest r = new PojoRest(a); ClassMeta<?> cm = r.getClassMeta(pathRemainder); Object in = req.getBody().asType(pathRemainder); r.put(pathInfo, in); return "PUT successful"; } catch (Exception e) { throw new RestException(SC_BAD_REQUEST, "PUT unsuccessful").initCause(e); } } /** * [INIT /] * Reinitialize this resource. */ @RestMethod(name="INIT", path="/", guards=AdminGuard.class ) public String doInit() throws Exception { init(); return "OK"; } /** * [GET /cognos] * Get data in Cognos/XML format */ @RestMethod(name=GET, path="/cognos") public DataSet getCognosData() throws Exception { // The Cognos metadata Column[] items = { new Column("name", "xs:String", 255), new Column("age", "xs:int"), new Column("numAddresses", "xs:int") .addTransform( new PojoSwap<Person,Integer>() { @Override /* PojoSwap */ public Integer swap(Person p) { return p.addresses.size(); } } ) }; return new DataSet(items, addressBook, this.getBeanContext()); } /** * [OPTIONS /*] * View resource options */ @Override /* RestServletDefault */ @RestMethod(name=OPTIONS, path="/*") public Swagger getOptions(RestRequest req) { return req.getSwagger(); } /** Convenience method - Find a person by ID */ private Person findPerson(int id) throws RestException { Person p = addressBook.findPerson(id); if (p == null) throw new RestException(SC_NOT_FOUND, "Person not found"); return p; } /** Convenience method - Find an address by ID */ private Address findAddress(int id) throws RestException { Address a = addressBook.findAddress(id); if (a == null) throw new RestException(SC_NOT_FOUND, "Address not found"); return a; } }

The generated OPTIONS page is shown below:

The OPTIONS page uses the servlet resource bundle to specify the labels so that they're globalizable.

AddressBookResource.properties

label = AddressBook sample resource description = Proof-of-concept resource that shows off the capabilities of working with POJO resources getRoot = Get root page getAllPeople = Get all people in the address book getAllPeople.res.200.content = List<Person> getPerson = Get a single person by ID getPerson.req.attr.id = Person UUID getPerson.res.200.content = Person bean getPerson.res.404 = Person ID not found getAllAddresses = Get all addresses in the address book getAllAddresses.res.200.content = List<Address> getAddress = Get a single address by ID getAddress.req.attr.id = Address UUID getAddress.res.200.content = Address bean getAddress.res.404 = Address ID not found createPerson = Create a new Person bean createPerson.res.307.header.Location = URL of new person createAddress = Create a new Address bean createAddress.req.attr.id = Person UUID createAddress.res.307.header.Location = URL of new address deletePerson = Delete a Person bean deletePerson.req.attr.id = Person UUID deletePerson.res.200.content = "DELETE successful" deletePerson.res.404 = Person ID not found deleteAddress = Delete an Address bean deleteAddress.req.attr.id = Address UUID deleteAddress.res.200.content = "DELETE successful" deleteAddress.res.404 = Address ID not found updatePerson = Change property on Person bean updatePerson.req.attr.id = Person UUID updatePerson.req.content = Anything updatePerson.res.200.content = "PUT successful" updatePerson.res.400 = Invalid object type used updatePerson.res.404 = Person ID not found updateAddress = Change property on Address bean updateAddress.req.attr.id = Address UUID updateAddress.req.content = Anything updateAddress.res.200.content = "PUT successful" updateAddress.res.400 = Invalid object type used updateAddress.res.404 = Address ID not found doInit = Reinitialize this resource doInit.res.200.content = "OK" getOptions = View resource options getCognosData = Get data in Cognos/XML format getCognosData.res.200.content = DataSet otherNotes = GZip support enabled. Public methods can be invoked by using the &Method URL parameter. 'text/cognos+xml' support available under root resource only

Skip navigation links

Copyright © 2017 Apache. All rights reserved.