Defines an API for defining REST resources as servlets.
A REST resource is an implementation of {@link org.apache.juneau.rest.RestServlet}, which itself is simply an
extension of {@link javax.servlet.http.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 {@link org.apache.juneau.rest.RestServletDefault} class that we're using here is a subclass of
{@link org.apache.juneau.rest.RestServlet} that provides default support for a variety of content types.
Implementers can choose to use this class, or create their own subclass of
{@link org.apache.juneau.rest.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.
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 {@link org.apache.juneau.rest.RestServlet} annotated with
{@link org.apache.juneau.rest.annotation.RestResource @RestResource} containing public Java methods
annotated with {@link org.apache.juneau.rest.annotation.RestMethod @RestMethod}.
Developers will typically subclass directly from {@link org.apache.juneau.rest.RestServletDefault}
since it provides a default set of serializers and parsers for a variety of Accept and
Content-Type types.
| Accept |
Content-Type |
Serializer |
application/json text/json |
application/json |
{@link org.apache.juneau.json.JsonSerializer} |
application/json+simple text/json+simple |
application/json |
{@link org.apache.juneau.json.JsonSerializer.Simple} |
application/json+schema text/json+schema |
application/json |
{@link org.apache.juneau.json.JsonSchemaSerializer} |
| text/xml |
text/xml |
{@link org.apache.juneau.xml.XmlDocSerializer} |
| text/xml+schema |
text/xml |
{@link org.apache.juneau.xml.XmlSchemaDocSerializer} |
| text/html |
text/html |
{@link org.apache.juneau.html.HtmlDocSerializer} |
| text/html+stripped |
text/html |
{@link org.apache.juneau.html.HtmlStrippedDocSerializer} |
| text/uon |
text/uon |
{@link org.apache.juneau.uon.UonSerializer} |
| application/x-www-form-urlencoded |
application/x-www-form-urlencoded |
{@link org.apache.juneau.urlencoding.UrlEncodingSerializer} |
| text/xml+soap |
text/xml |
{@link org.apache.juneau.soap.SoapXmlSerializer} |
| text/plain |
text/plain |
{@link org.apache.juneau.plaintext.PlainTextSerializer} |
| application/x-java-serialized-object |
application/x-java-serialized-object |
{@link org.apache.juneau.jso.JsoSerializer} |
| Content-Type |
Parser |
application/json text/json |
{@link org.apache.juneau.json.JsonParser} |
text/xml application/xml |
{@link org.apache.juneau.xml.XmlParser} |
text/html text/html+stripped |
{@link org.apache.juneau.html.HtmlParser} |
| text/uon |
{@link org.apache.juneau.uon.UonParser} |
| application/x-www-form-urlencoded |
{@link org.apache.juneau.urlencoding.UrlEncodingParser} |
| text/plain |
{@link org.apache.juneau.plaintext.PlainTextParser} |
{@link org.apache.juneau.rest.RestServletDefault} also provides a default OPTIONS page by implementing
a {@link org.apache.juneau.rest.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
-
{@link org.apache.juneau.rest.RestServletDefault}
4.1 - REST Java Method Signature
REST Java methods are identified on REST servlets using the
{@link org.apache.juneau.rest.annotation.RestMethod @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
{@link org.apache.juneau.rest.annotation.RestResource#messages() @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 {@link org.apache.juneau.rest.RestResponse#setOutput(Object)} method.
Calling the {@link org.apache.juneau.rest.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:
-
{@link java.io.InputStream}
The contents are simply piped to the output stream returned by
{@link org.apache.juneau.rest.RestResponse#getNegotiatedOutputStream()}.
Note that you should call {@link org.apache.juneau.rest.RestResponse#setContentType(String)} to set
the Content-Type header if you use this object type.
-
{@link java.io.Reader}
The contents are simply piped to the output stream returned by
{@link org.apache.juneau.rest.RestResponse#getNegotiatedWriter()}.
Note that you should call {@link org.apache.juneau.rest.RestResponse#setContentType(String)} to set
the Content-Type header if you use this object type.
-
{@link org.apache.juneau.rest.Redirect}
Represents an HTTP redirect response.
-
{@link org.apache.juneau.Streamable}
Interface that identifies that an object can be serialized directly to an output stream.
-
{@link org.apache.juneau.Writable}
Interface that identifies that an object can be serialized directly to a writer.
-
{@link org.apache.juneau.utils.ZipFileList}
Special interface for sending zip files as responses.
Additional "special types" can be defined through the {@link org.apache.juneau.rest.ResponseHandler}
interface (described later).
Method Parameters
The method can contain any of the following parameters in any order:
-
Parameters of the following class types:
- Request/response objects:
- {@link org.apache.juneau.rest.RestRequest} - The request object.
- {@link javax.servlet.http.HttpServletRequest} - The superclass of
RestRequest
.
- {@link org.apache.juneau.rest.RestResponse} - The response object.
- {@link javax.servlet.http.HttpServletResponse} - The superclass of
RestResponse
.
- Parsed request header values:
- {@link org.apache.juneau.http.Accept}
- {@link org.apache.juneau.http.AcceptCharset}
- {@link org.apache.juneau.http.AcceptEncoding}
- {@link org.apache.juneau.http.AcceptLanguage}
- {@link org.apache.juneau.http.Authorization}
- {@link org.apache.juneau.http.CacheControl}
- {@link org.apache.juneau.http.Connection}
- {@link org.apache.juneau.http.ContentLength}
- {@link org.apache.juneau.http.ContentType}
- {@link org.apache.juneau.http.Date}
- {@link org.apache.juneau.http.Expect}
- {@link org.apache.juneau.http.From}
- {@link org.apache.juneau.http.Host}
- {@link org.apache.juneau.http.IfMatch}
- {@link org.apache.juneau.http.IfModifiedSince}
- {@link org.apache.juneau.http.IfNoneMatch}
- {@link org.apache.juneau.http.IfRange}
- {@link org.apache.juneau.http.IfUnmodifiedSince}
- {@link org.apache.juneau.http.MaxForwards}
- {@link org.apache.juneau.http.Pragma}
- {@link org.apache.juneau.http.ProxyAuthorization}
- {@link org.apache.juneau.http.Range}
- {@link org.apache.juneau.http.Referer}
- {@link org.apache.juneau.http.TE}
- {@link org.apache.juneau.http.UserAgent}
- {@link org.apache.juneau.http.Upgrade}
- {@link org.apache.juneau.http.Via}
- {@link org.apache.juneau.http.Warning}
- {@link java.util.TimeZone}
- Direct streams on request/response:
- {@link java.io.InputStream}
- {@link javax.servlet.ServletInputStream}
- {@link java.io.Reader}
- {@link java.io.OutputStream}
- {@link javax.servlet.ServletOutputStream}
- {@link java.io.Writer}
- Localization:
- {@link java.util.ResourceBundle} - Client-localized resource bundle.
- {@link org.apache.juneau.utils.MessageBundle} - A resource bundle with additional features.
- {@link java.util.Locale} - Client locale.
- Request APIs:
- {@link org.apache.juneau.rest.RequestHeaders} - API for accessing request headers.
- {@link org.apache.juneau.rest.RequestQuery} - API for accessing request query parameters.
- {@link org.apache.juneau.rest.RequestFormData} - API for accessing request form data.
- {@link org.apache.juneau.rest.RequestPathMatch} - API for accessing path variables.
- {@link org.apache.juneau.rest.RequestBody} - API for accessing request body.
- Other:
- {@link org.apache.juneau.http.HttpMethod} - The method name matched (when using
@RestMethod(name="*")
)
- {@link java.util.logging.Logger} - The logger to use for logging.
- {@link org.apache.juneau.internal.JuneauLogger} - Logger with additional features.
- {@link org.apache.juneau.rest.RestContext} - The resource read-only context.
- {@link org.apache.juneau.parser.Parser} - The parser matching the request content type.
- {@link org.apache.juneau.dto.swagger.Swagger} - The auto-generated Swagger doc.
- {@link org.apache.juneau.ini.ConfigFile} - The external config file for the resource.
-
Annotated parameters:
- {@link org.apache.juneau.rest.annotation.Path @Path} - Variables in matched URL path patterns.
- {@link org.apache.juneau.rest.annotation.FormData @FormData} - Multipart form post parameter values.
- {@link org.apache.juneau.rest.annotation.HasFormData @HasFormData} - Denotes whether the form data parameter exists.
- {@link org.apache.juneau.rest.annotation.Query @Query} - Query parameters. Using this prevents the HTTP body from being processed as a URL-Encoded form post.
- {@link org.apache.juneau.rest.annotation.HasQuery @HasQuery} - Denotes whether the query parameter exists.
- {@link org.apache.juneau.rest.annotation.Header @Header} - A header value.
- {@link org.apache.juneau.rest.annotation.Method @Method} - The HTTP method name.
- {@link org.apache.juneau.rest.annotation.PathRemainder @PathRemainder} - The remainder value after path pattern match.
- {@link org.apache.juneau.rest.annotation.Body @Body} - The HTTP content parsed as a POJO.
// 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,
RequestProperties properties,
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 {@link org.apache.juneau.rest.RestRequest} class.
4.1.1 - Path
The {@link org.apache.juneau.rest.annotation.RestMethod#path() @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
{@link org.apache.juneau.rest.annotation.RestMethod#priority() @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
{@link org.apache.juneau.rest.RequestPathMatch#getRemainder()} or parameters with the
{@link org.apache.juneau.rest.annotation.PathRemainder @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
{@link org.apache.juneau.rest.RestMatcher 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
{@link org.apache.juneau.rest.RestRequest#getBody()} method, or a parameter annotated with
{@link org.apache.juneau.rest.annotation.Body @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
{@link org.apache.juneau.rest.RestRequest#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 {@link org.apache.juneau.rest.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:
-
By returning a serializable POJO, or any of the following:
{@link java.io.Reader}, {@link java.io.InputStream}, {@link org.apache.juneau.Streamable},
{@link org.apache.juneau.Writable}
-
By calling {@link org.apache.juneau.rest.RestResponse#setOutput(Object)} with any of the types above.
-
By accessing the {@link java.io.Writer} directly by calling
{@link org.apache.juneau.rest.RestResponse#getNegotiatedWriter()} and writing the output yourself.
// 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(RestContextBuilder builder) 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:
- {@link org.apache.juneau.rest.annotation.HookEvent#INIT INIT} - Right before initialization.
- {@link org.apache.juneau.rest.annotation.HookEvent#POST_INIT POST_INIT} - Right after initialization.
- {@link org.apache.juneau.rest.annotation.HookEvent#POST_INIT_CHILD_FIRST POST_INIT_CHILD_FIRST} - Right after initialization, but run child methods first.
- {@link org.apache.juneau.rest.annotation.HookEvent#DESTROY DESTROY} - Right before servlet destroy.
- REST call lifecycle events:
- {@link org.apache.juneau.rest.annotation.HookEvent#START_CALL START_CALL} - At the beginning of a REST call.
- {@link org.apache.juneau.rest.annotation.HookEvent#PRE_CALL PRE_CALL} - Right before the @RestMethod method is invoked.
- {@link org.apache.juneau.rest.annotation.HookEvent#POST_CALL POST_CALL} - Right after the @RestMethod method is invoked.
- {@link org.apache.juneau.rest.annotation.HookEvent#END_CALL END_CALL} - At the end of the REST call after the response has been flushed.
Additional Information
-
{@link org.apache.juneau.rest.annotation.RestHook @RestHook}
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).
This page is constructed through reflection on the servlet class and it's methods, combined with
information provided in the following locations:
- Annotations (i.e. {@link org.apache.juneau.rest.annotation.RestResource @RestResource} and
{@link org.apache.juneau.rest.annotation.RestMethod @RestMethod}).
- Resource bundle properties.
- Swagger JSON files.
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 {@link org.apache.juneau.dto.swagger.Swagger} bean.
This bean is then serialized just like any other POJO to produce the page shown above.
{@link org.apache.juneau.rest.RestServletDefault} provides a default OPTIONS page by implementing
a {@link org.apache.juneau.rest.RestServletDefault#getOptions(RestRequest)} method that returns a POJO
consisting of beans describing the class.
It uses the {@link org.apache.juneau.rest.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.
- Annotations: {@link org.apache.juneau.rest.annotation.RestResource#title() @RestResource.title()},
{@link org.apache.juneau.rest.annotation.RestResource#description() @RestResource.description()}
- By overriding methods on the servlet class:
{@link org.apache.juneau.rest.RestInfoProvider#getTitle(RestRequest)},
{@link org.apache.juneau.rest.RestInfoProvider#getDescription(RestRequest)}
- By defining properties in the resource bundle.
- By specifying them in a Swagger JSON file.
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
{@link org.apache.juneau.rest.annotation.RestResource#messages() @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:
-
{@link org.apache.juneau.rest.RestRequest#getServletTitle()}
-
{@link org.apache.juneau.rest.RestRequest#getServletDescription()}
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
{@link org.apache.juneau.rest.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 {@link org.apache.juneau.rest.RestInfoProvider#getTitle(RestRequest)}
and {@link org.apache.juneau.rest.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
{@link org.apache.juneau.rest.annotation.RestMethod#description() @RestMethod.description()},
{@link org.apache.juneau.rest.annotation.RestMethod#swagger() @RestMethod.swagger()},
and {@link org.apache.juneau.rest.annotation.MethodSwagger @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 |
{@link org.apache.juneau.rest.annotation.RestResource#title() @RestResource.title()} |
{@link org.apache.juneau.rest.RestInfoProvider#getTitle(RestRequest)} |
| description |
Servlet description |
{@link org.apache.juneau.rest.annotation.RestResource#description() @RestResource.description()} |
{@link org.apache.juneau.rest.RestInfoProvider#getDescription(RestRequest)} |
| [javaMethodName].summary |
Java method summary |
{@link org.apache.juneau.rest.annotation.RestMethod#summary() @RestMethod.summary()} |
| [javaMethodName].description |
Java method description |
{@link org.apache.juneau.rest.annotation.RestMethod#description() @RestMethod.description()} |
| [javaMethodName].req.body.description |
A description of the HTTP request body.
|
{@link org.apache.juneau.rest.annotation.MethodSwagger#parameters() @MethodSwagger.parameters()} |
| [javaMethodName].req.[category].[name].description |
A request input variable.
Categories: path, query, formData, header
|
{@link org.apache.juneau.rest.annotation.MethodSwagger#parameters() @MethodSwagger.parameters()} |
| [javaMethodName].res.[code].description |
A possible HTTP response code and description.
|
{@link org.apache.juneau.rest.annotation.MethodSwagger#responses() @MethodSwagger.responses()} |
| [javaMethodName].res.[code].body.description |
A description of response content for the specified HTTP response.
|
{@link org.apache.juneau.rest.annotation.MethodSwagger#responses() @MethodSwagger.responses()} |
| [javaMethodName].res.[code].header.[name].description |
A response header.
|
{@link org.apache.juneau.rest.annotation.MethodSwagger#responses() @MethodSwagger.responses()} |
Additional Information
-
{@link org.apache.juneau.rest.annotation.RestMethod#description() @RestMethod.description()}
-
{@link org.apache.juneau.rest.annotation.MethodSwagger#parameters() @MethodSwagger.parameters()}
-
{@link org.apache.juneau.rest.annotation.MethodSwagger#responses() @MethodSwagger.responses()}
4.6 - Serializers
REST servlets use the {@link org.apache.juneau.serializer.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 {@link org.apache.juneau.serializer.Serializer#getMediaTypes()} method.
Serializers can be associated with REST servlets in the following ways:
-
{@link org.apache.juneau.rest.annotation.RestResource#serializers() @RestResource.serializers()}
- Annotation on servlet class.
-
{@link org.apache.juneau.rest.annotation.RestMethod#serializers() @RestMethod.serializers()}
- Annotation on individual servlet methods.
-
{@link org.apache.juneau.rest.RestContextBuilder#serializers(Class[])}
- Override method to set the serializers programmatically.
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
{@link org.apache.juneau.serializer.WriterSerializer#toString(Object)} method.
Additional Information
-
{@link org.apache.juneau.rest.annotation.RestMethod#inherit() @RestMethod.inherit()}
Controls how serializers are inherited from the servlet class.
4.7 - Parsers
REST servlets use the {@link org.apache.juneau.parser.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 {@link org.apache.juneau.parser.Parser#getMediaTypes()} method.
Parsers can be associated with REST servlets in the following ways:
-
{@link org.apache.juneau.rest.annotation.RestResource#parsers() @RestResource.parsers()}
- Annotation on servlet class.
-
{@link org.apache.juneau.rest.annotation.RestMethod#parsers() @RestMethod.parsers()}
- Annotation on individual servlet methods.
-
{@link org.apache.juneau.rest.RestContextBuilder#parsers(Class[])}
- Override method to set the parsers programmatically.
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
-
{@link org.apache.juneau.rest.annotation.RestMethod#inherit() @RestMethod.inherit()}
Controls how parsers are inherited from the servlet class.
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
{@link org.apache.juneau.rest.annotation.RestResource#properties() @RestResource.properties()}
and {@link org.apache.juneau.rest.annotation.RestMethod#properties() @RestMethod.properties()} annotations.
The {@link org.apache.juneau.rest.annotation.RestResource#properties() @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.Serializer.*;
import static org.apache.juneau.xml.XmlSerializer.*;
import static org.apache.juneau.html.HtmlSerializer.*;
// 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 {@link org.apache.juneau.rest.annotation.RestMethod#properties() @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 {@link org.apache.juneau.rest.RestContext} class has a variety of properties for
controlling the behavior of the {@link org.apache.juneau.rest.RestServlet} class itself.
There are also ways to provide properties programmatically.
-
By overriding the {@link org.apache.juneau.rest.RestContextBuilder#serializers(Class[])} and
{@link org.apache.juneau.rest.RestContextBuilder#parsers(Class[])} methods and setting properties on the
serializers and parsers directly.
Additional Information
-
{@link org.apache.juneau.rest.RestContext}
Properties associated with the {@link org.apache.juneau.rest.RestServlet} class.
-
{@link org.apache.juneau.rest.annotation.RestMethod#inherit @RestMethod.inherit()}
Controls how serializers and parsers inherit properties from the servlet class.
4.9 - Transforms
The Juneau serializers and parsers can be configured on how to handle POJOs through the use of Transforms.
(See Transforms)
The {@link org.apache.juneau.rest.annotation.RestResource#beanFilters() @RestResource.beanFilters()}
and {@link org.apache.juneau.rest.annotation.RestResource#pojoSwaps() @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 {
...
}
{@link org.apache.juneau.rest.annotation.RestMethod#beanFilters() @RestMethod.beanFilters()} and
{@link org.apache.juneau.rest.annotation.RestMethod#pojoSwaps() @RestMethod.pojoSwaps()}
are the equivalent annotations for individual Java methods.
Transforms can also be defined programmatically through the following:
-
By overriding the {@link org.apache.juneau.rest.RestContextBuilder#beanFilters(Class[])} and
{@link org.apache.juneau.rest.RestContextBuilder#pojoSwaps(Class[])} methods.
-
By overriding the {@link org.apache.juneau.rest.RestContextBuilder#serializers(Class[])} and
{@link org.apache.juneau.rest.RestContextBuilder#parsers(Class[])} methods and setting transforms on the
serializers and parsers directly.
Additional Information
-
{@link org.apache.juneau.rest.annotation.RestMethod#inherit @RestMethod.inherit()}
Controls how serializers and parsers inherit transforms (bean filters and POJO swaps) from the servlet class.
4.10 - Guards
Guards are classes that control access to REST servlets and methods.
The {@link org.apache.juneau.rest.annotation.RestResource#guards @RestResource.guards()} annotation
can be used to associate one or more class-level {@link org.apache.juneau.rest.RestGuard 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
{@link org.apache.juneau.rest.RestGuard#guard(RestRequest,RestResponse)} and processing the response
yourself.
public class AdminGuard extends RestGuard {
@Override
public boolean guard(RestRequest req, RestResponse res) throws RestException {
if (! isOkay(req))
throw new RestException(SC_FORBIDDEN, "Access denied!!!");
return true;
}
}
When guards are associated at the class-level, it's equivalent to associating guards on all Java methods on
the servlet.
If multiple guards are present, ALL guards must pass.
Guards are registered through the following:
- {@link org.apache.juneau.rest.RestContext#REST_guards}
- {@link org.apache.juneau.rest.annotation.RestResource#guards()}
- {@link org.apache.juneau.rest.annotation.RestMethod#guards()}
- {@link org.apache.juneau.rest.RestContextBuilder#guards(Class...)}
- {@link org.apache.juneau.rest.RestContextBuilder#guards(RestGuard...)}
Additional Information
- {@link org.apache.juneau.rest.RestGuard}
4.11 - Converters
Converters can be thought of as a "post-processor" for POJOs before they get passed to the serializers.
The {@link org.apache.juneau.rest.annotation.RestResource#converters @RestResource.converters()} annotation
can be used as a convenient way to add {@link org.apache.juneau.rest.RestConverter 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 {@link org.apache.juneau.rest.annotation.RestMethod#converters() @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 {@link org.apache.juneau.utils.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(RestRequest req, Object o) throws RestException, SerializeException {
if (o == null)
return null;
String pathRemainder = req.getPathMatch().getRemainder();
if (pathRemainder != null) {
try {
// Use the PojoRest class to traverse our POJO model.
PojoRest p = new PojoRest(o);
o = p.get(pathRemainder);
} catch (PojoRestException e) {
throw new RestException(e.getStatus(), e);
} catch (Exception e) {
throw new RestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
}
}
return o;
}
}
Juneau defines the following converters out-of-the-box:
-
{@link org.apache.juneau.rest.RestConverter}
-
{@link org.apache.juneau.rest.converters.Queryable}
Provides query parameters that can be used to transform the response (i.e. search/view/sort the
POJO response before being serialized).
-
{@link org.apache.juneau.rest.converters.Traversable}
Allows nodes in the POJO response tree to be individually accessed through additional path info on
the request.
-
{@link org.apache.juneau.rest.converters.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.
Converters are registered through the following:
- {@link org.apache.juneau.rest.RestContext#REST_converters}
- {@link org.apache.juneau.rest.annotation.RestResource#converters()}
- {@link org.apache.juneau.rest.annotation.RestMethod#converters()}
- {@link org.apache.juneau.rest.RestContextBuilder#converters(Class...)}
- {@link org.apache.juneau.rest.RestContextBuilder#converters(RestConverter...)}
Additional Information
- {@link org.apache.juneau.rest.RestConverter}
4.12 - Child Resources
Child Resources are REST servlets that are linked to parent servlets through the
{@link org.apache.juneau.rest.annotation.RestResource#children() @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 {
...
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 {@link org.apache.juneau.rest.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
{@link org.apache.juneau.rest.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 are defined through the following:
- {@link org.apache.juneau.rest.RestContext#REST_children}
- {@link org.apache.juneau.rest.annotation.RestResource#children()}
- {@link org.apache.juneau.rest.RestContextBuilder#child(String,Object)}
- {@link org.apache.juneau.rest.RestContextBuilder#children(Class...)}
- {@link org.apache.juneau.rest.RestContextBuilder#children(Object...)}
4.13 - Localized Messages
The {@link org.apache.juneau.rest.annotation.RestResource#messages @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 simply specifying a parameter
of type {@link java.util.ResourceBundle} or {@link org.apache.juneau.utils.MessageBundle}:
@RestMethod(name=GET)
public String printLocalizedGreeting(MessageBundle 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!
Messages are registered through the following:
- {@link org.apache.juneau.rest.RestContext#REST_messages}
- {@link org.apache.juneau.rest.annotation.RestResource#messages()}
- {@link org.apache.juneau.rest.RestContextBuilder#messages(String)},
- {@link org.apache.juneau.rest.RestContextBuilder#messages(Class,String)}
- {@link org.apache.juneau.rest.RestContextBuilder#messages(MessageBundleLocation...)}
4.14- Encoders
The {@link org.apache.juneau.rest.annotation.RestResource#encoders @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.
// Servlet with automated support for GZIP compression
@RestResource(encoders={GzipEncoder.class})
public MyRestServlet extends RestServlet {
...
}
Juneau defines the following encoders out-of-the-box:
-
{@link org.apache.juneau.encoders.Encoder}
-
{@link org.apache.juneau.encoders.GzipEncoder}
-
{@link org.apache.juneau.encoders.IdentityEncoder}
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 {@link org.apache.juneau.svl.VarResolver} class and one or more
{@link org.apache.juneau.svl.Var Vars}.
// 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:
-
{@link org.apache.juneau.rest.RestServlet}
-
{@link org.apache.juneau.rest.RestContext#getVarResolver()}
Returns the VarResolver associated with a servlet.
-
{@link org.apache.juneau.rest.RestContextBuilder#vars(Class[])}
The method used to create the servlet VarResolver.
Subclasses can override this method to provide their own resolver.
-
{@link org.apache.juneau.rest.RestCallHandler#getSessionObjects(RestRequest)}
Defines the session objects for the var resolver.
Subclasses can override this method to provide additional session objects.
The default {@link org.apache.juneau.rest.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 {@link org.apache.juneau.rest.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.
@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 {@link org.apache.juneau.rest.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 {@link org.apache.juneau.rest.RestRequest#getMessage(String, Object...)}.
Can include message arguments (e.g. "$L{my.localized.string, foo, bar}")
|
| $RA{key1[,key2]} |
Request attribute variables.
Value returned by {@link org.apache.juneau.rest.RestRequest#getAttribute(String)} converted to a string.
First non-null value is returned.
For security reasons, embedded or returned variables are not recursively resolved.
|
| $RF{key1[,key2]} |
Request form-data variables.
Value returned by {@link org.apache.juneau.rest.RestRequest#getFormData(String)} converted to a string.
First non-null value is returned.
For security reasons, embedded or returned variables are not recursively resolved.
|
| $RH{key1[,key2]} |
Request header variables.
Value returned by {@link org.apache.juneau.rest.RestRequest#getHeader(String)} converted to a string.
First non-null value is returned.
For security reasons, embedded or returned variables are not recursively resolved.
|
| $RI{key1[,key2]} |
Rest info variables.
Values returned by {@link org.apache.juneau.rest.RestRequest#getInfoProvider()} converted to a string.
First non-null value is returned.
For security reasons, embedded or returned variables are not recursively resolved.
Possible values:
- $RI{contact} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getContact(RestRequest)}.
- $RI{description} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getDescription(RestRequest)}.
- $RI{externalDocs} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getExternalDocs(RestRequest)}.
- $RI{license} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getLicense(RestRequest)}.
- $RI{methodDescription} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getMethodDescription(Method,RestRequest)}.
- $RI{methodSummary} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getMethodSummary(Method,RestRequest)}.
- $RI{siteName} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getSiteName(RestRequest)}.
- $RI{tags} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getTags(RestRequest)}.
- $RI{termsOfService} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getTermsOfService(RestRequest)}.
- $RI{title} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getTitle(RestRequest)}.
- $RI{version} - Value returned by {@link org.apache.juneau.rest.RestInfoProvider#getVersion(RestRequest)}.
|
| $RP{key1[,key2]} |
Request path variables.
Value returned by {@link org.apache.juneau.rest.RestRequest#getPath(String)} converted to a string.
First non-null value is returned.
For security reasons, embedded or returned variables are not recursively resolved.
|
| $RQ{key1[,key2]} |
Request query parameter variables.
Value returned by {@link org.apache.juneau.rest.RestRequest#getQuery(String)} converted to a string.
First non-null value is returned.
For security reasons, embedded or returned variables are not recursively resolved.
|
| $RQ{key1[,key2]} |
Request object variables.
This variable allows access to values found on the {@link org.apache.juneau.rest.RestRequest} object.
First non-null value is returned.
For security reasons, embedded or returned variables are not recursively resolved.
Possible values:
- $R{method} - Value returned by {@link org.apache.juneau.rest.RestRequest#getMethod()}.
- $R{methodSummary} - Value returned by {@link org.apache.juneau.rest.RestRequest#getMethodSummary()}.
- $R{methodDescription} - Value returned by {@link org.apache.juneau.rest.RestRequest#getMethodDescription()}.
- $R{pathInfo} - Value returned by {@link org.apache.juneau.rest.RestRequest#getPathInfo()}.
- $R{requestParentURI} - Value returned by {@link org.apache.juneau.UriContext#getRootRelativePathInfoParent()}.
- $R{requestURI} - Value returned by {@link org.apache.juneau.rest.RestRequest#getRequestURI()}.
- $R{servletDescription} - Value returned by {@link org.apache.juneau.rest.RestRequest#getServletDescription()}.
- $R{servletTitle} - Value returned by {@link org.apache.juneau.rest.RestRequest#getServletTitle()}.
- $R{servletParentURI} - Value returned by {@link org.apache.juneau.UriContext#getRootRelativeServletPathParent()}.
- $R{servletPath} - Value returned by {@link org.apache.juneau.rest.RestRequest#getServletPath()}.
- $R{servletURI} - Value returned by {@link org.apache.juneau.UriContext#getRootRelativeServletPath()}.
|
| $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 {@link org.apache.juneau.rest.RestRequest#getClasspathReaderResource(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.
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>
/**
* 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.getClasspathReaderResource("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;
}
}
#--------------------------------------------------------------------------------
# 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
-
{@link org.apache.juneau.rest.ReaderResource}
-
{@link org.apache.juneau.rest.RestRequest#getClasspathReaderResource(String,boolean)}
-
org.apache.juneau.svl
4.16 - Static Files
The {@link org.apache.juneau.rest.annotation.RestResource#staticFiles @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.
package com.foo.mypackage;
@RestResource(
path="/myresource",
staticFiles={"htdocs:docs"}
)
public class MyResource extends RestServletDefault {
}
Static files are found by calling {@link java.lang.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:
- com.foo.mypackage.docs package.
- org.apache.juneau.rest.docs package (since RestServletDefault is in
org.apache.juneau.rest).
- [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
{@link org.apache.juneau.rest.RestContext#getMediaTypeForName(String)} method.
4.17 - Listener Methods
Various convenience listener methods can be implemented by using the {@link org.apache.juneau.rest.annotation.RestHook @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:
-
{@link org.apache.juneau.rest.annotation.RestResource#defaultRequestHeaders() @RestResource.defaultRequestHeaders()}
Defines default headers on request when the client doesn't specify them.
-
{@link org.apache.juneau.rest.annotation.RestResource#defaultResponseHeaders() @RestResource.defaultResponseHeaders()}
Appends the specified headers if they weren't already set programmatically.
// 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:
-
{@link org.apache.juneau.rest.RestContextBuilder}
-
{@link org.apache.juneau.rest.RestContextBuilder#defaultRequestHeaders(String[])}
-
{@link org.apache.juneau.rest.RestContextBuilder#defaultResponseHeaders(String[])}
4.20 - Handling Errors and Logging
The {@link org.apache.juneau.rest.RestContext#REST_logger} property allows you to configure
logging for your resource.
The interface is shown below:
-
{@link org.apache.juneau.rest.RestLogger}
-
{@link org.apache.juneau.rest.RestLogger#log(Level,String,Object[]) log(Level,String,Object[])}
-
{@link org.apache.juneau.rest.RestLogger#log(Level,Throwable,String,Object[]) log(Level,Throwable,String,Object[])}
-
{@link org.apache.juneau.rest.RestLogger#logObjects(Level,String,Object[]) logObjects(Level,String,Object[])}
-
{@link org.apache.juneau.rest.RestLogger#onError(HttpServletRequest,HttpServletResponse,RestException) onError(HttpServletRequest,HttpServletResponse,RestException)}
Gets called when an error occurs on a request call.
Default implementation logs the error.
The {@link org.apache.juneau.rest.RestLogger#logObjects(Level,String,Object[]) logObjects()} method is particularly useful because it allows you to pass in POJOs as arguments
that serialized using {@link org.apache.juneau.json.JsonSerializer#DEFAULT_LAX_READABLE}, but only
if the message is actually logged.
logger.logObjects(DEBUG, "Pojo contents:\n{0}", myPojo);
By default, the Juneau framework uses the built-in Java Logging API for logging.
But you can define your own implementation to use any framework you wish.
The {@link org.apache.juneau.rest.RestLogger} instance is accessible via the following:
- {@link org.apache.juneau.rest.RestContext#getLogger()}
- {@link org.apache.juneau.rest.RestRequest#getLogger()}
In addition, the logger can be accessed by passing it as a parameter to your REST java method:
@RestMethod()
public Object doSomething(RestLogger logger) {...}
If your resource extends from {@link org.apache.juneau.rest.RestServlet}, you can also
use and override the following methods:
- {@link org.apache.juneau.rest.RestServlet#log(Level,String,Object...)}
- {@link org.apache.juneau.rest.RestServlet#log(Level,Throwable,String,Object...)}
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 {@link org.apache.juneau.ini.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
{@link org.apache.juneau.rest.annotation.RestResource#config() @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 {@link org.apache.juneau.microservice.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 {@link org.apache.juneau.microservice.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
{@link org.apache.juneau.rest.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
{@link org.apache.juneau.rest.RestRequest#getConfigFile()} to access the config file:
#-------------------------------------
# Contents of config_dir/myconfig.cfg
#-------------------------------------
[HelloWorldResource]
message = Hello $RQ{person}!
/**
* 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]
message = $L{localizedMessage,$RQ{person}}
#-------------------------------------------
# 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 |
| {@link org.apache.juneau.rest.annotation.RestResource#guards() 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.
|
| {@link org.apache.juneau.rest.annotation.RestResource#converters() 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.
|
| {@link org.apache.juneau.rest.annotation.RestResource#beanFilters() beanFilters()} |
Bean filters on child are combined with those on parent class.
|
| {@link org.apache.juneau.rest.annotation.RestResource#pojoSwaps() pojoSwaps()} |
POJO swaps on child are combined with those on parent class.
|
| {@link org.apache.juneau.rest.annotation.RestResource#properties() 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.
|
| {@link org.apache.juneau.rest.annotation.RestResource#serializers() serializers()} |
Serializers on child are combined with those on parent class.
Serializers on methods take precedence over those on classes.
|
| {@link org.apache.juneau.rest.annotation.RestResource#parsers() parsers()} |
Parsers on child are combined with those on parent class.
Parsers on methods take precedence over those on classes.
|
| {@link org.apache.juneau.rest.annotation.RestResource#responseHandlers() responseHandlers()} |
Response handlers on child are combined with those on parent class.
|
| {@link org.apache.juneau.rest.annotation.RestResource#encoders() encoders()} |
Encoders on child are combined with those on parent class.
|
| {@link org.apache.juneau.rest.annotation.RestResource#defaultRequestHeaders() 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.
|
| {@link org.apache.juneau.rest.annotation.RestResource#defaultResponseHeaders() 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.
|
| {@link org.apache.juneau.rest.annotation.RestResource#children() 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.
|
| {@link org.apache.juneau.rest.annotation.RestResource#path() path()} |
Path is searched for in child-to-parent order.
|
| {@link org.apache.juneau.rest.annotation.RestResource#title() title()} |
Label is searched for in child-to-parent order.
|
| {@link org.apache.juneau.rest.annotation.RestResource#description() description()} |
Description is searched for in child-to-parent order.
|
| {@link org.apache.juneau.rest.annotation.RestResource#config() config()} |
Config file is searched for in child-to-parent order.
|
| {@link org.apache.juneau.rest.annotation.RestResource#staticFiles() 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 {@link org.apache.juneau.rest.RestException} with a
specific HTTP status code, or calling {@link javax.servlet.http.HttpServletResponse#setStatus(int)}.
Non-OK (200) status codes are automatically triggered by the following...
| Code |
Description |
When triggered |
| 401 |
Unauthorized |
A {@link org.apache.juneau.rest.RestGuard 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
{@link org.apache.juneau.rest.RestMatcher 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 {@link org.apache.juneau.rest.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 {@link org.apache.juneau.rest.annotation.RestResource#allowedMethodParams() @RestResource.allowedMethodParams()}
setting must be enabled on your servlet.
@RestResource(
// Allow &method parameter on BAR requests
allowedMethodParams="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
({@link org.apache.juneau.serializer.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 {@link org.apache.juneau.rest.annotation.RestResource#allowedMethodParams() @RestResource.allowedMethodParams()} setting.
|
| &X=headerValue |
Specify a header value as a GET parameter.
Must be enabled via {@link org.apache.juneau.rest.annotation.RestResource#allowHeaderParams() @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 {@link org.apache.juneau.rest.annotation.RestResource#allowBodyParam() @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 {@link org.apache.juneau.rest.ResponseHandler ResponseHandlers} to convert POJOS returned by
REST methods to proper HTTP responses.
Juneau comes with the following response handlers out-of-the-box:
-
{@link org.apache.juneau.rest.ResponseHandler}
-
{@link org.apache.juneau.rest.response.DefaultHandler}
The default handler for POJOs that convert them to output using the Juneau serializers.
-
{@link org.apache.juneau.rest.response.ReaderHandler}
Pipes {@link java.io.Reader Readers} directly to the response stream.
-
{@link org.apache.juneau.rest.response.InputStreamHandler}
Pipes {@link java.io.InputStream InputStream} directly to the response stream.
-
{@link org.apache.juneau.rest.response.RedirectHandler}
Handles {@link org.apache.juneau.rest.Redirect} objects that perform HTTP redirection
(HTTP 302).
-
{@link org.apache.juneau.rest.response.WritableHandler}
Handles {@link org.apache.juneau.Writable} objects.
-
{@link org.apache.juneau.rest.response.StreamableHandler}
Handles {@link org.apache.juneau.Streamable} objects.
-
{@link org.apache.juneau.rest.response.ZipFileListResponseHandler}
Handles {@link org.apache.juneau.utils.ZipFileList} objects.
The list of response handlers can be modified or augmented in one of the following ways:
-
Specifying additional handlers through the
{@link org.apache.juneau.rest.annotation.RestResource#responseHandlers() @RestResource.responseHandlers()}
annotation.
-
Overriding and extending the {@link org.apache.juneau.rest.RestContextBuilder#responseHandlers(Class[])}
method.
The {@link org.apache.juneau.rest.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 {@link javax.servlet.http.HttpServlet#init(ServletConfig)}
or {@link javax.servlet.http.HttpServlet#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}"
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:
/**
* 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.
allowedMethodParams="*",
// 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<EMAIL_ADDRESS>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.
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