MethodExampleResource
The MethodExampleResource class provides examples of the following:
-
Using the various Java method parameter annotations to retrieve request attributes, parameters, etc.
-
Using the annotation programmatic equivalents on the {@link oajr.RestRequest} object.
-
Setting response POJOs by either returning them or using the
{@link oajr.RestResponse#setOutput(Object)} method.
The resource is provided to show how various HTTP entities (e.g. parameters, headers) can be accessed
as either annotated Java parameters, or through methods on the RestRequest object.
/**
* Sample REST resource that shows how to define REST methods and OPTIONS pages
*/
@RestResource(
path="/methodExample",
messages="nls/MethodExampleResource",
htmldoc=@HtmlDoc(
navlinks={
"up: servlet:/..",
"options: servlet:/?method=OPTIONS",
"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
},
aside={
"<div style='max-width:400px' class='text'>",
" <p>Shows the different methods for retrieving HTTP query/form-data parameters, headers, and path variables.</p>",
" <p>Each method is functionally equivalent but demonstrate different ways to accomplish the same tasks.</p>",
"</div>"
}
)
)
public class MethodExampleResource extends BasicRestServlet {
private static final UUID SAMPLE_UUID = UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
private static final String SAMPLE_UUID_STRING = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
/** Example GET request that redirects to our example method */
@RestMethod(name=GET, path="/")
public ResourceDescription[] doExample() throws Exception {
return new ResourceDescription[] {
new ResourceDescription(
"example1/foo/123/"+SAMPLE_UUID+"/path-remainder?q1=456&q2=bar",
"Example 1 - Annotated method attributes."
),
new ResourceDescription(
"example2/foo/123/"+SAMPLE_UUID+"/path-remainder?q1=456&q2=bar",
"Example 2 - Low-level RestRequest/RestResponse objects."
),
new ResourceDescription(
"example3/foo/123/"+SAMPLE_UUID+"/path-remainder?q1=456&q2=bar",
"Example 3 - Intermediate-level APIs."
)
};
}
/**
* Methodology #1 - GET request using annotated attributes.
* This approach uses annotated parameters for retrieving input.
*/
@RestMethod(name=GET, path="/example1/{p1}/{p2}/{p3}/*")
public String example1(
@Method String method, // HTTP method.
@Path("p1") String p1, // Path variables.
@Path("p2") int p2,
@Path("p3") UUID p3,
@Query("q1") int q1, // Query parameters.
@Query("q2") String q2,
@Query("q3") UUID q3,
@Path("/*") String remainder, // Path remainder after pattern match.
@Header("Accept-Language") String lang, // Headers.
@Header("Accept") String accept,
@Header("DNT") int doNotTrack
) {
// Send back a simple Map response
return new AMap<String,Object>()
.append("method", method)
.append("path-p1", p1)
.append("path-p2", p2)
.append("path-p3", p3)
.append("remainder", remainder)
.append("query-q1", q1)
.append("query-q2", q2)
.append("query-q3", q3)
.append("header-lang", lang)
.append("header-accept", accept)
.append("header-doNotTrack", doNotTrack);
}
/**
* Methodology #2 - GET request using methods on RestRequest and RestResponse.
* This approach uses low-level request/response objects to perform the same as above.
*/
@RestMethod(name=GET, path="/example2/{p1}/{p2}/{p3}/*")
public void example2(
RestRequest req, // A direct subclass of HttpServletRequest.
RestResponse res // A direct subclass of HttpServletResponse.
) {
// HTTP method.
String method = req.getMethod();
// Path variables.
RequestPathMatch path = req.getPathMatch();
String p1 = path.get("p1", String.class);
int p2 = path.get("p2", int.class);
UUID p3 = path.get("p3", UUID.class);
// Query parameters.
RequestQuery query = req.getQuery();
int q1 = query.get("q1", 0, int.class);
String q2 = query.get("q2", String.class);
UUID q3 = query.get("q3", UUID.class);
// Path remainder after pattern match.
String remainder = req.getPathMatch().getRemainder();
// Headers.
String lang = req.getHeader("Accept-Language");
String accept = req.getHeader("Accept");
int doNotTrack = req.getHeaders().get("DNT", int.class);
// Send back a simple Map response
Map<String,Object> m = new AMap<String,Object>()
.append("method", method)
.append("path-p1", p1)
.append("path-p2", p2)
.append("path-p3", p3)
.append("remainder", remainder)
.append("query-q1", q1)
.append("query-q2", q2)
.append("query-q3", q3)
.append("header-lang", lang)
.append("header-accept", accept)
.append("header-doNotTrack", doNotTrack);
res.setOutput(m); // Use setOutput(Object) just to be different.
}
/**
* Methodology #3 - GET request using special objects.
* This approach uses intermediate-level APIs.
* The framework recognizes the parameter types and knows how to resolve them.
*/
@RestMethod(name=GET, path="/example3/{p1}/{p2}/{p3}/*")
public String example3(
HttpMethod method, // HTTP method.
RequestPathMatch path, // Path variables.
RequestQuery query, // Query parameters.
RequestHeaders headers, // Headers.
AcceptLanguage lang, // Specific header classes.
Accept accept
) {
// Path variables.
String p1 = path.get("p1", String.class);
int p2 = path.get("p2", int.class);
UUID p3 = path.get("p3", UUID.class);
// Query parameters.
int q1 = query.get("q1", 0, int.class);
String q2 = query.get("q2", String.class);
UUID q3 = query.get("q3", UUID.class);
// Path remainder after pattern match.
String remainder = path.getRemainder();
// Headers.
int doNotTrack = headers.get("DNT", int.class);
// Send back a simple Map response
return new AMap<String,Object>()
.append("method", method)
.append("path-p1", p1)
.append("path-p2", p2)
.append("path-p3", p3)
.append("remainder", remainder)
.append("query-q1", q1)
.append("query-q2", q2)
.append("query-q3", q3)
.append("header-lang", lang)
.append("header-accept", accept)
.append("header-doNotTrack", doNotTrack);
}
}
The class consists of 4 methods:
-
doExample()
The root page.
-
example1()
Shows how to use the following annotations:
- {@link oaj.http.annotation.Path @Path}
- {@link oaj.http.annotation.Query @Query}
- {@link oaj.http.annotation.Header @Header}
- {@link oajr.annotation.Method @Method}
Method returns a POJO to be serialized as the output.
-
example2()
Identical to doGetExample1() but shows how to use the
{@link oajr.RestRequest} and {@link oajr.RestResponse} objects:
- {@link oajr.RestRequest#getPathMatch()}
- {@link oajr.RestRequest#getQuery()}
- {@link oajr.RestRequest#getFormData()}
- {@link oajr.RestRequest#getHeaders()}
- {@link oajr.RestRequest#getMethod()}
- {@link oajr.RequestPath#getRemainder()}
Method sets the POJO to be serialized using the {@link oajr.RestResponse#setOutput(Object)} method.
-
example3()
Identical to doGetExample1() but uses automatically resolved parameters based on class type.
Juneau automatically recognizes specific class types such as common header types and automatically
resolves them to objects for you.
See {@doc juneau-rest-server.RestMethod @RestMethod} for the list of all automatically support parameter types, and
{@link oajr.annotation.RestResource#paramResolvers() @RestResource(paramResolvers)}
for defining your own custom parameter type resolvers.
There's a lot going on in this method.
Notice how you're able to access URL attributes, parameters, headers, and content as parsed POJOs.
All the input parsing is already done by the toolkit.
You simply work with the resulting POJOs.
As you might notice, using annotations typically results in fewer lines of code and are therefore usually
preferred over the API approach, but both are equally valid.
When you visit this page through the router page, you can see the top level page:
http://localhost:10000/methodExample
Clicking the first link on the page results in this page:
http://localhost:10000/methodExample/example1/foo/123/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/path-remainder?q1=456&q2=bar
Notice how the conversion to POJOs is automatically done for us, even for non-standard POJOs such as UUID.
Self-documenting design through Swagger OPTIONS pages
One of the main features of Juneau is that it produces OPTIONS pages for self-documenting design (i.e. REST
interfaces that document themselves).
Much of the information populated on the OPTIONS page is determined through reflection.
This basic information can be augmented with information defined through:
-
Annotations - An example of this was shown in the
SystemPropertiesResource example above.
Localized strings can be pulled from resource bundles using the $L localization variable.
-
Resource bundle properties - See
MethodExampleResource.properties in the source.
-
Swagger JSON files with the same name and location as the resource class (e.g.
MethodExampleResource.json).
Localized versions are defined by appending the locale to the file name (e.g.
MethodExampleResource_ja_JP.json);
OPTIONS pages are simply serialized {@link oaj.dto.swagger.Swagger} DTO beans.
Localized versions of these beans are retrieved using the
{@link oajr.RestRequest#getSwagger()} method.
To define an OPTIONS request handler, the {@link oajr.BasicRestServlet} class defines
the following Java method:
/** OPTIONS request handler */
@RestMethod(name=OPTIONS, path="/*")
public Swagger getOptions(RestRequest req) {
return req.getSwagger();
}
The OPTIONS link that you see on the HTML version of the page is created
through a property defined by the {@link oaj.html.HtmlDocSerializer} class
and specified on the resource class annotation:
@RestResource(
htmldoc=@HtmlDoc(
navlinks={
"options: servlet:/?method=OPTIONS"
}
)
)
This simply creates a link that's the same URL as the resource URL appended with "?method=OPTIONS",
which is a shorthand way that the framework provides of defining overloaded GET requests.
Links using relative or absolute URLs can be defined this way.
Clicking the options link on the page presents you with information about how to use this resource:
http://localhost:10000/methodExample/?method=OPTIONS
This page (like any other) can also be rendered in JSON or XML by using the &Accept URL parameter.