org.apache.juneau.rest.RestContext Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.rest;
import static javax.servlet.http.HttpServletResponse.*;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.IOUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import java.io.*;
import java.lang.reflect.*;
import java.lang.reflect.Method;
import java.nio.charset.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;
import javax.activation.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.juneau.*;
import org.apache.juneau.config.*;
import org.apache.juneau.encoders.*;
import org.apache.juneau.html.*;
import org.apache.juneau.http.*;
import org.apache.juneau.http.annotation.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.httppart.bean.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.msgpack.*;
import org.apache.juneau.oapi.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.plaintext.*;
import org.apache.juneau.remote.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.converters.*;
import org.apache.juneau.rest.exception.*;
import org.apache.juneau.rest.helper.*;
import org.apache.juneau.rest.reshandlers.*;
import org.apache.juneau.rest.util.*;
import org.apache.juneau.rest.vars.*;
import org.apache.juneau.rest.widget.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.soap.*;
import org.apache.juneau.svl.*;
import org.apache.juneau.uon.*;
import org.apache.juneau.urlencoding.*;
import org.apache.juneau.utils.*;
import org.apache.juneau.xml.*;
import org.apache.juneau.xmlschema.*;
/**
* Contains all the configuration on a REST resource and the entry points for handling REST calls.
*
* See Also:
*
* - {@doc juneau-rest-server.RestContext}
*
*/
public final class RestContext extends BeanContext {
//-------------------------------------------------------------------------------------------------------------------
// Configurable properties
//-------------------------------------------------------------------------------------------------------------------
private static final String PREFIX = "RestContext.";
/**
* Configuration property: Allow body URL parameter.
*
* Property:
*
* - Name:
"RestContext.allowBodyParam.b"
* - Data type:
Boolean
* - Default:
true
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#allowBodyParam()}
*
* - Methods:
*
* - {@link RestContextBuilder#allowBodyParam(boolean)}
*
*
*
* Description:
*
* When enabled, the HTTP body content on PUT and POST requests can be passed in as text using the "body"
* URL parameter.
*
* For example:
*
* ?body=(name='John%20Smith',age=45)
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (allowBodyParam="$C{REST/allowBodyParam,false}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.allowBodyParam(false );
*
* // Same, but using property.
* builder.set(REST_allowBodyParam , false );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.allowBodyParam(false );
* }
* }
*
*
* Notes:
*
* -
*
'body' parameter name is case-insensitive.
* -
* Useful for debugging PUT and POST methods using only a browser.
*
*/
public static final String REST_allowBodyParam = PREFIX + "allowBodyParam.b";
/**
* Configuration property: Allowed method parameters.
*
* Property:
*
* - Name:
"RestContext.allowedMethodParams.s"
* - Data type:
String
* - Default:
"HEAD,OPTIONS"
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#allowedMethodParams()}
*
* - Methods:
*
* - {@link RestContextBuilder#allowedMethodParams(String...)}
*
*
*
* Description:
*
* When specified, the HTTP method can be overridden by passing in a "method" URL parameter on a regular
* GET request.
*
* For example:
*
* ?method=OPTIONS
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (allowMethodParams="$C{REST/allowMethodParams,HEAD\,OPTIONS\,PUT}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.allowMethodParams("HEAD,OPTIONS,PUT" );
*
* // Same, but using property.
* builder.set(REST_allowMethodParams , "HEAD,OPTIONS,PUT" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.allowMethodParams("HEAD" , "OPTIONS" , "PUT" );
* }
* }
*
*
* Notes:
*
* -
* Format is a comma-delimited list of HTTP method names that can be passed in as a method parameter.
*
-
*
'method' parameter name is case-insensitive.
* -
* Use
"*" to represent all methods.
*
*
*
* Note that per the {@doc RFC2616.section9 HTTP specification}, special care should
* be taken when allowing non-safe (POST, PUT, DELETE) methods to be invoked through GET requests.
*/
public static final String REST_allowedMethodParams = PREFIX + "allowedMethodParams.s";
/**
* Configuration property: Allow header URL parameters.
*
*
Property:
*
* - Name:
"RestContext.allowHeaderParams.b"
* - Data type:
Boolean
* - Default:
true
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#allowHeaderParams()}
*
* - Methods:
*
* - {@link RestContextBuilder#allowHeaderParams(boolean)}
*
*
*
* Description:
*
* When enabled, headers such as "Accept" and "Content-Type" to be passed in as URL query
* parameters.
*
* For example:
*
* ?Accept=text/json&Content-Type=text/json
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (allowMethodParams="$C{REST/allowHeaderParams,false}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.allowHeaderParams(false );
*
* // Same, but using property.
* builder.set(REST_allowHeaderParams , false );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.allowHeaderParams(false );
* }
* }
*
*
* Notes:
*
* -
* Header names are case-insensitive.
*
-
* Useful for debugging REST interface using only a browser.
*
*/
public static final String REST_allowHeaderParams = PREFIX + "allowHeaderParams.b";
/**
* Configuration property: REST call handler.
*
* Property:
*
* - Name:
"RestContext.callHandler.o"
* - Data type: {@link RestCallHandler} |
Class<? extends {@link RestCallHandler}>
* - Default: {@link BasicRestCallHandler}
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#callHandler()}
*
* - Methods:
*
* - {@link RestContextBuilder#callHandler(Class)}
*
- {@link RestContextBuilder#callHandler(RestCallHandler)}
*
*
*
* Description:
*
* This class handles the basic lifecycle of an HTTP REST call.
*
Subclasses can be used to customize how these HTTP calls are handled.
*
*
Example:
*
* // Our customized call handler.
* public class MyRestCallHandler extends BasicRestCallHandler {
*
* // Must provide this constructor!
* public MyRestCallHandler(RestContext context) {
* super (context);
* }
*
* @Override
* public RestRequest createRequest(HttpServletRequest req) throws ServletException {
* // Low-level handling of requests.
* ...
* }
*
* @Override
* public void handleResponse(RestRequest req, RestResponse res, Object output) throws IOException, RestException {
* // Low-level handling of responses.
* ...
* }
*
* @Override
* public void handleNotFound(int rc, RestRequest req, RestResponse res) throws Exception {
* // Low-level handling of various error conditions.
* ...
* }
* }
*
* // Option #1 - Registered via annotation resolving to a config file setting with default value.
* @RestResource (callHandler=MyRestCallHandler.class )
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.callHandler(MyRestCallHandler.class );
*
* // Same, but using property.
* builder.set(REST_callHandler , MyRestCallHandler.class );
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.callHandler(MyRestCallHandler.class );
* }
* }
*
*
* Notes:
*
* -
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(RestContext)
* public T()
*
* -
* Inner classes of the REST resource class are allowed.
*
*/
public static final String REST_callHandler = PREFIX + "callHandler.o";
/**
* Configuration property: Children.
*
* Property:
*
* - Name:
"RestContext.children.lo"
* - Data type:
List<Class | Object | {@link RestChild}>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#children()}
*
* - Methods:
*
* - {@link RestContextBuilder#child(String,Object)}
*
- {@link RestContextBuilder#children(Class...)}
*
- {@link RestContextBuilder#children(Object...)}
*
*
*
* Description:
*
* Defines children of this resource.
*
*
* A REST child resource is simply another servlet or object that is initialized as part of the ascendant resource and has a
* servlet path directly under the ascendant resource object path.
*
The main advantage to defining servlets as REST children is that you do not need to define them in the
* web.xml
file of the web application.
*
This can cut down on the number of entries that show up in the web.xml
file if you are defining
* large numbers of servlets.
*
*
* Child resources must specify a value for {@link RestResource#path() @RestResource(path)} that identifies the subpath of the child resource
* relative to the ascendant path UNLESS you use the {@link RestContextBuilder#child(String, Object)} method to register it.
*
*
* Child resources can be nested arbitrarily deep using this technique (i.e. children can also have children).
*
*
* - Servlet initialization:
* -
*
* A child resource will be initialized immediately after the ascendant servlet/resource is initialized.
*
The child resource receives the same servlet config as the ascendant servlet/resource.
*
This allows configuration information such as servlet initialization parameters to filter to child
* resources.
*
*
* - Runtime behavior:
* -
*
* As a rule, methods defined on the HttpServletRequest
object will behave as if the child
* servlet were deployed as a top-level resource under the child's servlet path.
*
For example, the getServletPath()
and getPathInfo()
methods on the
* HttpServletRequest
object will behave as if the child resource were deployed using the
* child's servlet path.
*
Therefore, the runtime behavior should be equivalent to deploying the child servlet in the
* web.xml
file of the web application.
*
*
*
*
* Example:
*
* // Our child resource.
* @RestResource (path="/child" )
* public class MyChildResource {...}
*
* // Option #1 - Registered via annotation.
* @RestResource (children={MyChildResource.class })
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.children(MyChildResource.class );
*
* // Same, but using property.
* builder.addTo(REST_children , MyChildResource.class ));
*
* // Use a pre-instantiated object instead.
* builder.child("/child" , new MyChildResource());
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.children(MyChildResource.class );
* }
* }
*
*
Notes:
*
* -
* When defined as classes, instances are resolved using the registered {@link #REST_resourceResolver} which
* by default is {@link BasicRestResourceResolver} which requires the class have one of the following
* constructors:
*
* public T(RestContextBuilder)
* public T()
*
*
*
* See Also:
*
* - {@doc juneau-rest-server.Instantiation.Children}
*
*/
public static final String REST_children = PREFIX + "children.lo";
/**
* Configuration property: Classpath resource finder.
*
* Property:
*
* - Name:
"RestContext.classpathResourceFinder.o"
* - Data type: {@link ClasspathResourceFinder}
*
- Default: {@link ClasspathResourceFinderBasic}
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#classpathResourceFinder()}
*
* - Methods:
*
* - {@link RestContextBuilder#classpathResourceFinder(Class)}
*
- {@link RestContextBuilder#classpathResourceFinder(ClasspathResourceFinder)}
*
*
*
* Description:
*
* Used to retrieve localized files from the classpath.
*
*
* Used by the following methods:
*
* - {@link RestContext}
*
* - {@link #getClasspathResource(String,Locale) getClasspathResource(String,Locale)}
*
- {@link #getClasspathResource(Class,String,Locale) getClasspathResource(Class,String,Locale)}
*
- {@link #getClasspathResource(Class,MediaType,String,Locale) getClasspathResource(Class,MediaType,String,Locale)}
*
- {@link #getClasspathResource(Class,Class,MediaType,String,Locale) getClasspathResource(Class,Class,MediaType,String,Locale)}
*
- {@link #getClasspathResourceAsString(String,Locale) getClasspathResourceAsString(String,Locale)}
*
- {@link #getClasspathResourceAsString(Class,String,Locale) getClasspathResourceAsString(Class,String,Locale)}
*
- {@link #resolveStaticFile(String) resolveStaticFile(String)}
*
* - {@link RestRequest}
*
* - {@link RestRequest#getClasspathReaderResource(String) getClasspathReaderResource(String)}
*
- {@link RestRequest#getClasspathReaderResource(String,boolean) getClasspathReaderResource(String,boolean)}
*
- {@link RestRequest#getClasspathReaderResource(String,boolean,MediaType) getClasspathReaderResource(String,boolean,MediaType)}
*
*
*
*
* It also affects the behavior of the {@link #REST_staticFiles} property.
*
*
Example:
*
* // Our customized classpath resource finder.
* public class MyClasspathResourceFinder extends ClasspathResourceFinderBasic {
* @Override
* public InputStream findResource(Class<?> baseClass, String name, Locale locale) throws IOException {
* // Do your own resolution.
* }
* }
*
* // Option #1 - Registered via annotation.
* @RestResource (classpathResourceFinder=MyClasspathResourceFinder.class )
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.classpathResourceFinder(MyClasspathResourceFinder.class );
*
* // Same, but using property.
* builder.set(REST_classpathResourceFinder , MyClasspathResourceFinder.class ));
*
* // Use a pre-instantiated object instead.
* builder.classpathResourceFinder(new MyClasspathResourceFinder());
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.classpathResourceFinder(MyClasspathResourceFinder.class );
* }
* }
*
*
Notes:
*
* -
* The default value is {@link ClasspathResourceFinderBasic} which provides basic support for finding localized
* resources on the classpath and JVM working directory.
*
The {@link ClasspathResourceFinderRecursive} is another option that also recursively searches for resources
* up the class-hierarchy.
*
Each of these classes can be extended to provide customized handling of resource retrieval.
* -
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(RestContext)
* public T()
*
* -
* Inner classes of the REST resource class are allowed.
*
*/
public static final String REST_classpathResourceFinder = PREFIX + "classpathResourceFinder.o";
/**
* Configuration property: Client version header.
*
* Property:
*
* - Name:
"RestContext.clientVersionHeader.s"
* - Data type:
String
* - Default:
"X-Client-Version"
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#clientVersionHeader()}
*
* - Methods:
*
* - {@link RestContextBuilder#clientVersionHeader(String)}
*
*
*
* Description:
*
* Specifies the name of the header used to denote the client version on HTTP requests.
*
*
* The client version is used to support backwards compatibility for breaking REST interface changes.
*
Used in conjunction with {@link RestMethod#clientVersion() @RestMethod(clientVersion)} annotation.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (clientVersionHeader="$C{REST/clientVersionHeader,Client-Version}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.clientVersionHeader("Client-Version" );
*
* // Same, but using property.
* builder.set(REST_clientVersionHeader , "Client-Version" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.clientVersionHeader("Client-Version" );
* }
* }
*
*
* // Call this method if Client-Version is at least 2.0.
* // Note that this also matches 2.0.1.
* @RestMethod (name=GET , path="/foobar" , clientVersion="2.0" )
* public Object method1() {
* ...
* }
*
* // Call this method if Client-Version is at least 1.1, but less than 2.0.
* @RestMethod (name=GET , path="/foobar" , clientVersion="[1.1,2.0)" )
* public Object method2() {
* ...
* }
*
* // Call this method if Client-Version is less than 1.1.
* @RestMethod (name=GET , path="/foobar" , clientVersion="[0,1.1)" )
* public Object method3() {
* ...
* }
*
*/
public static final String REST_clientVersionHeader = PREFIX + "clientVersionHeader.s";
/**
* Configuration property: Class-level response converters.
*
* Property:
*
* - Name:
"RestContext.converters.lo"
* - Data type:
List<{@link RestConverter} | Class<? extends {@link RestConverter}>>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#converters()}
*
- {@link RestMethod#converters()}
*
* - Methods:
*
* - {@link RestContextBuilder#converters(Class...)}
*
- {@link RestContextBuilder#converters(RestConverter...)}
*
*
*
* Description:
*
* Associates one or more {@link RestConverter converters} with a resource class.
*
These converters get called immediately after execution of the REST method in the same order specified in the
* annotation.
*
The object passed into this converter is the object returned from the Java method or passed into
* the {@link RestResponse#setOutput(Object)} method.
*
*
* Can be used for performing post-processing on the response object before serialization.
*
*
* When multiple converters are specified, they're executed in the order they're specified in the annotation
* (e.g. first the results will be traversed, then the resulting node will be searched/sorted).
*
*
Example:
*
* // Our converter.
* public class MyConverter implements RestConverter {
* @Override
* public Object convert(RestRequest req, Object o) {
* // Do something with object and return another object.
* // Or just return the same object for a no-op.
* }
* }
*
* // Option #1 - Registered via annotation resolving to a config file setting with default value.
* @RestResource (converters={MyConverter.class })
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.converters(MyConverter.class );
*
* // Same, but using property.
* builder.set(REST_converters , MyConverter.class );
*
* // Pass in an instance instead.
* builder.converters(new MyConverter());
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.converters(MyConverter.class );
* }
* }
*
*
* See Also:
*
* - {@link Traversable} - Allows URL additional path info to address individual elements in a POJO tree.
*
- {@link Queryable} - Allows query/view/sort functions to be performed on POJOs.
*
- {@link Introspectable} - Allows Java public methods to be invoked on the returned POJOs.
*
*
* See Also:
*
* - {@doc juneau-rest-server.Converters}
*
*
* Notes:
*
* -
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(BeanContext)
* public T()
*
* -
* Inner classes of the REST resource class are allowed.
*
*/
public static final String REST_converters = PREFIX + "converters.lo";
/**
* Configuration property: Debug mode.
*
* Property:
*
* - Name:
"RestContext.debug.b"
* - Data type:
Boolean
* - Default:
false
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#debug()}
*
* - Methods:
*
* - {@link RestContextBuilder#debug(boolean)}
*
*
*
* Description:
*
* Enables the following:
*
* - A message and stack trace is printed to STDERR when {@link BasicRestCallHandler#handleError(HttpServletRequest, HttpServletResponse, Throwable)} is called.
*
*/
public static final String REST_debug = PREFIX + "debug.b";
/**
* Configuration property: Default character encoding.
*
* Property:
*
* - Name:
"RestContext.defaultCharset.s"
* - Data type:
String
* - Default:
"utf-8"
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#defaultCharset()}
*
- {@link RestMethod#defaultCharset()}
*
* - Methods:
*
* - {@link RestContextBuilder#defaultCharset(String)}
*
- {@link RestContextBuilder#defaultCharset(Charset)}
*
*
*
* Description:
*
* The default character encoding for the request and response if not specified on the request.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (defaultCharset="$C{REST/defaultCharset,US-ASCII}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.defaultCharset("US-ASCII" );
*
* // Same, but using property.
* builder.set(REST_defaultCharset , "US-ASCII" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.defaultCharset("US-ASCII" );
* }
*
* // Override at the method level.
* @RestMethod (defaultCharset="UTF-16" )
* public Object myMethod() {...}
* }
*
*/
public static final String REST_defaultCharset = PREFIX + "defaultCharset.s";
/**
* Configuration property: Default request headers.
*
* Property:
*
* - Name:
"RestContext.defaultRequestHeaders.smo"
* - Data type:
Map<String,String>
* - Default: empty map
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#defaultRequestHeaders()}
*
- {@link RestMethod#defaultRequestHeaders()}
*
* - Methods:
*
* - {@link RestContextBuilder#defaultRequestHeader(String,Object)}
*
- {@link RestContextBuilder#defaultRequestHeaders(String...)}
*
*
*
* Description:
*
* Specifies default values for request headers if they're not passed in through the request.
*
*
Notes:
*
* -
* Strings are in the format
"Header-Name: header-value" .
* -
* Affects values returned by {@link RestRequest#getHeader(String)} when the header is not present on the request.
*
-
* The most useful reason for this annotation is to provide a default
Accept
header when one is not
* specified so that a particular default {@link Serializer} is picked.
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (defaultRequestHeaders={"Accept: application/json" , "My-Header: $C{REST/myHeaderValue}" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder
* .defaultRequestHeader("Accept" , "application/json" );
* .defaultRequestHeaders("My-Header: foo" );
*
* // Same, but using property.
* builder.addTo(REST_defaultRequestHeaders , "Accept" , "application/json" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.defaultRequestHeader("Accept" , "application/json" );
* }
*
* // Override at the method level.
* @RestMethod (defaultRequestHeaders={"Accept: text/xml" })
* public Object myMethod() {...}
* }
*
*/
public static final String REST_defaultRequestHeaders = PREFIX + "defaultRequestHeaders.smo";
/**
* Configuration property: Default response headers.
*
* Property:
*
* - Name:
"RestContext.defaultResponseHeaders.omo"
* - Data type:
Map<String,String>
* - Default: empty map
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#defaultResponseHeaders()}
*
* - Methods:
*
* - {@link RestContextBuilder#defaultResponseHeader(String,Object)}
*
- {@link RestContextBuilder#defaultResponseHeaders(String...)}
*
*
*
* Description:
*
* Specifies default values for response headers if they're not set after the Java REST method is called.
*
*
Notes:
*
* -
* Strings are in the format
"Header-Name: header-value" .
* -
* This is equivalent to calling {@link RestResponse#setHeader(String, String)} programmatically in each of
* the Java methods.
*
-
* The header value will not be set if the header value has already been specified (hence the 'default' in the name).
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (defaultResponseHeaders={"Content-Type: $C{REST/defaultContentType,text/plain}" ,"My-Header: $C{REST/myHeaderValue}" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder
* .defaultResponseHeader("Content-Type" , "text/plain" );
* .defaultResponseHeaders("My-Header: foo" );
*
* // Same, but using property.
* builder
* .addTo(REST_defaultRequestHeaders , "Accept" , "application/json" );
* .addTo(REST_defaultRequestHeaders , "My-Header" , "foo" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.defaultResponseHeader("Content-Type" , "text/plain" );
* }
* }
*
*/
public static final String REST_defaultResponseHeaders = PREFIX + "defaultResponseHeaders.omo";
/**
* Configuration property: Compression encoders.
*
* Property:
*
* - Name:
"RestContext.encoders.o"
* - Data type:
List<{@link Encoder} | Class<? extends {@link Encoder}>>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#encoders()}
*
- {@link RestMethod#encoders()}
*
* - Methods:
*
* - {@link RestContextBuilder#encoders(Class...)}
*
- {@link RestContextBuilder#encoders(Encoder...)}
*
*
*
* Description:
*
* These can be used to enable various kinds of compression (e.g. "gzip" ) on requests and responses.
*
*
Example:
*
* // Option #1 - Registered via annotation.
* @RestResource (encoders={GzipEncoder.class })
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.encoders(GzipEncoder.class );
*
* // Same, but using property.
* builder.addTo(REST_encoders , GzipEncoder.class );
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.encoders(GzipEncoder.class );
* }
*
* // Override at the method level.
* @RestMethod (encoders={MySpecialEncoder.class }, inherit={"ENCODERS" })
* public Object myMethod() {...}
* }
*
*
* Notes:
*
* -
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(BeanContext)
* public T()
*
* -
* Inner classes of the REST resource class are allowed.
*
*
* See Also:
*
* - {@doc juneau-rest-server.Encoders}
*
*/
public static final String REST_encoders = PREFIX + "encoders.lo";
/**
* Configuration property: Class-level guards.
*
* Property:
*
* - Name:
"RestContext.guards.lo"
* - Data type:
List<{@link RestGuard} | Class<? extends {@link RestGuard}>>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#guards()}
*
- {@link RestMethod#guards()}
*
* - Methods:
*
* - {@link RestContextBuilder#guards(Class...)}
*
- {@link RestContextBuilder#guards(RestGuard...)}
*
*
*
* Description:
*
* Associates one or more {@link RestGuard RestGuards} with all REST methods defined in this class.
*
These guards get called immediately before execution of any REST method in this class.
*
*
* If multiple guards are specified, ALL guards must pass.
*
Note that this is different than matchers were only ONE matcher needs to pass.
*
*
Example:
*
* // 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" );
* }
* }
*
* // Option #1 - Registered via annotation.
* @RestResource (guards={BillyGuard.class })
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.guards(BillyGuard.class );
*
* // Same, but using property.
* builder.addTo(REST_guards , BillyGuard.class );
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.guards(BillyGuard.class );
* }
*
* // Override at the method level.
* @RestMethod (guards={SomeOtherGuard.class })
* public Object myMethod() {...}
* }
*
*
* Notes:
*
* -
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(RestContext)
* public T()
*
* -
* Inner classes of the REST resource class are allowed.
*
*
* See Also:
*
* - {@doc juneau-rest-server.Guards}
*
*/
public static final String REST_guards = PREFIX + "guards.lo";
/**
* Configuration property: REST info provider.
*
* Property:
*
* - Name:
"RestContext.infoProvider.o"
* - Data type:
{@link RestInfoProvider} | Class<? extends {@link RestInfoProvider}>
* - Default: {@link BasicRestInfoProvider}
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#infoProvider()}
*
* - Methods:
*
* - {@link RestContextBuilder#infoProvider(Class)}
*
- {@link RestContextBuilder#infoProvider(RestInfoProvider)}
*
*
*
* Description:
*
* Class used to retrieve title/description/swagger information about a resource.
*
*
Example:
*
* // Our customized info provider.
* // Extend from the default implementation and selectively override values.
* public class MyRestInfoProvider extends BasicRestInfoProvider {
*
* // Must provide this constructor!
* public MyRestInfoProvider(RestContext context) {
* super (context);
* }
*
* @Override
* public Swagger getSwaggerFromFile(RestRequest req) throws RestException {
* // Provide our own method of retrieving swagger from file system.
* }
*
* @Override
* public Swagger getSwagger(RestRequest req) throws RestException {
* Swagger s = super .getSwagger(req);
* // Made inline modifications to generated swagger.
* return s;
* }
*
* @Override
* public String getSiteName(RestRequest req) {
* // Override the site name.
* }
* }
*
* // Option #1 - Registered via annotation resolving to a config file setting with default value.
* @RestResource (infoProvider=MyRestInfoProvider.class )
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.infoProvider(MyRestInfoProvider.class );
*
* // Same, but using property.
* builder.set(REST_infoProvider , MyRestInfoProvider.class );
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.infoProvider(MyRestInfoProvider.class );
* }
* }
*
*
* Notes:
*
* -
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(RestContext)
* public T()
*
* -
* Inner classes of the REST resource class are allowed.
*
*/
public static final String REST_infoProvider = PREFIX + "infoProvider.o";
/**
* Configuration property: REST logger.
*
* Property:
*
* - Name:
"RestContext.logger.o"
* - Data type:
{@link RestLogger} | Class<? extends {@link RestLogger}>
* - Default: {@link BasicRestLogger}
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#logger()}
*
* - Methods:
*
* - {@link RestContextBuilder#logger(Class)}
*
- {@link RestContextBuilder#logger(RestLogger)}
*
*
*
* Description:
*
* Specifies the logger to use for logging.
*
*
* Two implementations are provided by default:
*
* - {@link BasicRestLogger} - Default logging.
*
- {@link NoOpRestLogger} - Logging disabled.
*
*
*
* Loggers are accessible through the following:
*
* - {@link RestContext#getLogger() RestContext.getLogger()}
*
- {@link RestRequest#getLogger() RestRequest.getLogger()}
*
*
* Example:
*
* // Our customized logger.
* public class MyRestLogger extends BasicRestLogger {
*
* @Override
* public void log(Level level, Throwable cause, String msg, Object...args) {
* // Handle logging ourselves.
* }
* }
*
* // Option #1 - Registered via annotation resolving to a config file setting with default value.
* @RestResource (logger=MyRestLogger.class )
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.logger(MyRestLogger.class );
*
* // Same, but using property.
* builder.set(REST_logger , MyRestLogger.class );
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.logger(MyRestLogger.class );
* }
* }
*
*
* See Also:
*
* - {@doc juneau-rest-server.LoggingAndErrorHandling}
*
*/
public static final String REST_logger = PREFIX + "logger.o";
/**
* Configuration property: The maximum allowed input size (in bytes) on HTTP requests.
*
* Property:
*
* - Name:
"RestContext.maxInput.s"
* - Data type:
String
* - Default:
"100M"
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#maxInput()}
*
- {@link RestMethod#maxInput()}
*
* - Methods:
*
* - {@link RestContextBuilder#maxInput(String)}
*
*
*
* Description:
*
* Useful for alleviating DoS attacks by throwing an exception when too much input is received instead of resulting
* in out-of-memory errors which could affect system stability.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (maxInput="$C{REST/maxInput,10M}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.maxInput("10M" );
*
* // Same, but using property.
* builder.set(REST_maxInput , "10M" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.maxInput("10M" );
* }
*
* // Override at the method level.
* @RestMethod (maxInput="10M" )
* public Object myMethod() {...}
* }
*
*
* Notes:
*
* -
* String value that gets resolved to a
long .
* -
* Can be suffixed with any of the following representing kilobytes, megabytes, and gigabytes:
*
'K' , 'M' , 'G' .
* -
* A value of
"-1" can be used to represent no limit.
*
*/
public static final String REST_maxInput = PREFIX + "maxInput.s";
/**
* Configuration property: Messages.
*
* Property:
*
* - Name:
"RestContext.messages.lo"
* - Data type:
List<{@link MessageBundleLocation}>
* - Default:
null
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#messages()}
*
* - Methods:
*
* - {@link RestContextBuilder#messages(String)},
*
- {@link RestContextBuilder#messages(Class,String)}
*
- {@link RestContextBuilder#messages(MessageBundleLocation...)}
*
*
*
* Description:
*
* Identifies the location of the resource bundle for this class.
*
*
* This annotation is used to provide localized messages for the following methods:
*
* - {@link RestRequest#getMessage(String, Object...)}
*
- {@link RestContext#getMessages() RestContext.getMessages()}
*
*
*
* Messages are also available by passing either of the following parameter types into your Java method:
*
* - {@link ResourceBundle} - Basic Java resource bundle.
*
- {@link MessageBundle} - Extended resource bundle with several convenience methods.
*
*
*
* Messages passed into Java methods already have their locale set to that of the incoming request.
*
*
* The value can be a relative path like "nls/Messages" , indicating to look for the resource bundle
* "com.foo.sample.nls.Messages" if the resource class is in "com.foo.sample" , or it can be an
* absolute path like "com.foo.sample.nls.Messages"
*
*
Examples:
*
* package org.apache.foo;
*
* // Resolve messages to org/apache/foo/nls/MyMessages.properties
* @RestResource (messages="nls/MyMessages" )
* public class MyResource {...}
*
* @RestMethod (name="GET" , path="/hello/{you}" )
* public Object helloYou(RestRequest req, MessageBundle messages, @Path ("name" ) String you)) {
* String s;
*
* // Get it from the RestRequest object.
* s = req.getMessage("HelloMessage" , you);
*
* // Or get it from the method parameter.
* s = messages.getString("HelloMessage" , you);
*
* // Or get the message in a locale different from the request.
* s = messages.getString(Locale.UK , "HelloMessage" , you);
*
* return s;
* }
* }
*
*
* Notes:
*
* - Mappings are cumulative from super classes.
*
Therefore, you can find and retrieve messages up the class-hierarchy chain.
*
*
* See Also:
*
* - {@doc juneau-rest-server.Messages}
*
*/
public static final String REST_messages = PREFIX + "messages.lo";
/**
* Configuration property: MIME types.
*
* Property:
*
* - Name:
"RestContext.mimeTypes.ss"
* - Data type:
Set<String>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#mimeTypes()}
*
* - Methods:
*
* - {@link RestContextBuilder#mimeTypes(String...)}
*
*
*
* Description:
*
* Defines MIME-type file type mappings.
*
*
* Used for specifying the content type on file resources retrieved through the following methods:
*
* - {@link RestContext#resolveStaticFile(String) RestContext.resolveStaticFile(String)}
*
- {@link RestRequest#getClasspathReaderResource(String,boolean,MediaType)}
*
- {@link RestRequest#getClasspathReaderResource(String,boolean)}
*
- {@link RestRequest#getClasspathReaderResource(String)}
*
*
*
* This list appends to the existing list provided by {@link ExtendedMimetypesFileTypeMap}.
*
*
Example:
*
* // Option #1 - Defined via annotation.
* @RestResource (mimeTypes={"text/plain txt text TXT" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.mimeTypes("text/plain txt text TXT" );
*
* // Same, but using property.
* builder.addTo(REST_mimeTypes , "text/plain txt text TXT" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.mimeTypes("text/plain txt text TXT" );
* }
* }
*
*
* Notes:
*
* -
* Values are .mime.types formatted entry string.
*
Example: "image/svg+xml svg"
*
*/
public static final String REST_mimeTypes = PREFIX + "mimeTypes.ss";
/**
* Configuration property: Java method parameter resolvers.
*
* Property:
*
* - Name:
"RestContext.paramResolvers.lo"
* - Data type:
List<{@link RestMethodParam} | Class<? extends {@link RestMethodParam}>>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#paramResolvers()}
*
* - Methods:
*
* - {@link RestContextBuilder#paramResolvers(Class...)}
*
- {@link RestContextBuilder#paramResolvers(RestMethodParam...)}
*
*
*
* Description:
*
* By default, the Juneau framework will automatically Java method parameters of various types (e.g.
* RestRequest
, Accept
, Reader
).
* This setting allows you to provide your own resolvers for your own class types that you want resolved.
*
*
* For example, if you want to pass in instances of MySpecialObject
to your Java method, define
* the following resolver:
*
* // Define a parameter resolver for resolving MySpecialObject objects.
* public class MyRestParam extends RestMethodParam {
*
* // Must have no-arg constructor!
* public MyRestParam() {
* // First two parameters help with Swagger doc generation.
* super (QUERY , "myparam" , MySpecialObject.class );
* }
*
* // The method that creates our object.
* // In this case, we're taking in a query parameter and converting it to our object.
* public Object resolve(RestRequest req, RestResponse res) throws Exception {
* return new MySpecialObject(req.getQuery().get("myparam" ));
* }
* }
*
* // Option #1 - Registered via annotation.
* @RestResource (paramResolvers=MyRestParam.class )
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.paramResolvers(MyRestParam.class );
*
* // Same, but using property.
* builder.addTo(REST_paramResolver , MyRestParam.class );
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.paramResolvers(MyRestParam.class );
* }
*
* // Now pass it into your method.
* @RestMethod (...)
* public Object doMyMethod(MySpecialObject mySpeciaObject) {
* // Do something with it.
* }
* }
*
*
* Notes:
*
* -
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(BeanContext)
* public T()
*
* -
* Inner classes of the REST resource class are allowed.
*
-
* Refer to {@link RestMethodParam} for the list of predefined parameter resolvers.
*
*/
public static final String REST_paramResolvers = PREFIX + "paramResolvers.lo";
/**
* Configuration property: Parsers.
*
* Property:
*
* - Name:
"RestContext.parsers.lo"
* - Data type:
List<{@link Parser} | Class<? extends {@link Parser}>>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#parsers()}
*
- {@link RestMethod#parsers()}
*
* - Methods:
*
* - {@link RestContextBuilder#parsers(Object...)}
*
- {@link RestContextBuilder#parsers(Class...)}
*
- {@link RestContextBuilder#parsers(boolean,Object...)}
*
*
*
* Description:
*
* Adds class-level parsers to this resource.
*
*
* Parsers are used to convert the body of HTTP requests into POJOs.
*
Any of the Juneau framework parsers can be used in this setting.
*
The parser selected is based on the request Content-Type
header matched against the values returned by the following method
* using a best-match algorithm:
*
* - {@link Parser#getMediaTypes()}
*
*
* Example:
*
* // Option #1 - Defined via annotation.
* @RestResource (parsers={JsonParser.class , XmlParser.class })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.parsers(JsonParser.class , XmlParser.class );
*
* // Same, but use pre-instantiated parsers.
* builder.parsers(JsonParser.DEFAULT , XmlParser.DEFAULT );
*
* // Same, but using property.
* builder.set(REST_parsers , JsonParser.class , XmlParser.class );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.parsers(JsonParser.class , XmlParser.class );
* }
*
* // Override at the method level.
* @RestMethod (parsers={HtmlParser.class })
* public Object myMethod(@Body MyPojo myPojo) {
* // Do something with your parsed POJO.
* }
* }
*
*
* Notes:
*
* -
* When defined as a class, properties/transforms defined on the resource/method are inherited.
*
-
* When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
*
-
* Typically, you'll want your resource to extend directly from {@link BasicRestServlet} which comes
* preconfigured with the following parsers:
*
* - {@link JsonParser}
*
- {@link XmlParser}
*
- {@link HtmlParser}
*
- {@link UonParser}
*
- {@link UrlEncodingParser}
*
- {@link MsgPackParser}
*
- {@link PlainTextParser}
*
*
*
* See Also:
*
* - {@doc juneau-rest-server.Parsers}
*
*/
public static final String REST_parsers = PREFIX + "parsers.lo";
/**
* Configuration property: HTTP part parser.
*
* Property:
*
* - Name:
"RestContext.partParser.o"
* - Data type:
{@link HttpPartParser} | Class<? extends {@link HttpPartParser}>
* - Default: {@link OpenApiParser}
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#partParser()}
*
* - Methods:
*
* - {@link RestContextBuilder#partParser(Class)}
*
- {@link RestContextBuilder#partParser(HttpPartParser)}
*
*
*
* Description:
*
* Specifies the {@link HttpPartParser} to use for parsing headers, query/form parameters, and URI parts.
*
*
* The default value is {@link OpenApiParser} which allows for both plain-text and URL-Encoded-Object-Notation values.
*
If your parts contain text that can be confused with UON (e.g. "(foo)" ), you can switch to
* {@link SimplePartParser} which treats everything as plain text.
*
*
Example:
*
* // Option #1 - Defined via annotation.
* @RestResource (partParser=SimplePartParser.class )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.partParser(SimplePartParser.class );
*
* // Same, but using property.
* builder.set(REST_partParser , SimplePartParser.class );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.partParser(SimplePartParser.class );
* }
*
* @RestMethod (...)
* public Object myMethod(@Header ("My-Header" ) MyParsedHeader h, @Query ("myquery" ) MyParsedQuery q) {
* // Do something with your parsed parts.
* }
* }
*
*
* Notes:
*
* -
* When defined as a class, properties/transforms defined on the resource/method are inherited.
*
-
* When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
*
*/
public static final String REST_partParser = PREFIX + "partParser.o";
/**
* Configuration property: HTTP part serializer.
*
* Property:
*
* - Name:
"RestContext.partSerializer.o"
* - Data type:
{@link HttpPartSerializer} | Class<? extends {@link HttpPartSerializer}>
* - Default: {@link OpenApiSerializer}
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#partSerializer()}
*
* - Methods:
*
* - {@link RestContextBuilder#partSerializer(Class)}
*
- {@link RestContextBuilder#partSerializer(HttpPartSerializer)}
*
*
*
* Description:
*
* Specifies the {@link HttpPartSerializer} to use for serializing headers, query/form parameters, and URI parts.
*
*
* The default value is {@link OpenApiSerializer} which serializes based on OpenAPI rules, but defaults to UON notation for beans and maps, and
* plain text for everything else.
*
Other options include:
*
* - {@link SimplePartSerializer} - Always serializes to plain text.
*
- {@link UonSerializer} - Always serializers to UON.
*
*
* Example:
*
* // Option #1 - Defined via annotation.
* @RestResource (partSerializer=SimplePartSerializer.class )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.partSerializer(SimplePartSerializer.class );
*
* // Same, but using property.
* builder.set(REST_partSerializer , SimplePartSerializer.class );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.partSerializer(SimplePartSerializer.class );
* }
*
* @RestMethod (...)
* public Object myMethod(RestResponse res) {
* // Set a header to a POJO.
* res.setHeader("My-Header" , new MyPojo());
* }
* }
*
*
* Notes:
*
* -
* When defined as a class, properties/transforms defined on the resource/method are inherited.
*
-
* When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
*
*/
public static final String REST_partSerializer = PREFIX + "partSerializer.o";
/**
* Configuration property: Resource path.
*
* Property:
*
* - Name:
"RestContext.path.s"
* - Data type:
String
* - Default:
null
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#path()}
*
* - Methods:
*
* - {@link RestContextBuilder#path(String)}
*
*
*
* Description:
*
* Identifies the URL subpath relative to the ascendant resource.
*
*
* This setting is critical for the routing of HTTP requests from ascendant to child resources.
*
*
Example:
*
* // Option #1 - Defined via annotation.
* @RestResource (path="/myResource" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.path("/myResource" );
*
* // Same, but using property.
* builder.set(REST_path , "/myResource" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.path("/myResource" );
* }
* }
*
*
*
*
Notes:
*
* -
* This annotation is ignored on top-level servlets (i.e. servlets defined in
web.xml
files).
*
Therefore, implementers can optionally specify a path value for documentation purposes.
* -
* Typically, this setting is only applicable to resources defined as children through the
* {@link RestResource#children() @RestResource(children)} annotation.
*
However, it may be used in other ways (e.g. defining paths for top-level resources in microservices).
* -
* Slashes are trimmed from the path ends.
*
As a convention, you may want to start your path with '/' simple because it make it easier to read.
* -
* This path is available through the following method:
*
* - {@link RestContext#getPath() RestContext.getPath()}
*
*
*/
public static final String REST_path = PREFIX + "path.s";
/**
* Configuration property: Render response stack traces in responses.
*
* Property:
*
* - Name:
"RestContext.renderResponseStackTraces.b"
* - Data type:
Boolean
* - Default:
false
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#renderResponseStackTraces()}
*
* - Methods:
*
* - {@link RestContextBuilder#renderResponseStackTraces(boolean)}
*
- {@link RestContextBuilder#renderResponseStackTraces()}
*
*
*
* Description:
*
* Render stack traces in HTTP response bodies when errors occur.
*
*
Example:
*
* // Option #1 - Defined via annotation.
* @RestResource (renderResponseStackTraces=true )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.renderResponseStackTraces();
*
* // Same, but using property.
* builder.set(REST_renderResponseStackTraces , true );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.renderResponseStackTraces();
* }
* }
*
*
* Notes:
*
* -
* Useful for debugging, although allowing stack traces to be rendered may cause security concerns so use
* caution when enabling.
*
-
* This setting is available through the following method:
*
* - {@link RestContext#isRenderResponseStackTraces() RestContext.isRenderResponseStackTraces()}
*
* That method is used by {@link BasicRestCallHandler#handleError(HttpServletRequest, HttpServletResponse, Throwable)}.
*
*/
public static final String REST_renderResponseStackTraces = PREFIX + "renderResponseStackTraces.b";
/**
* Configuration property: REST resource resolver.
*
* Property:
*
* - Name:
"RestContext.resourceResolver.o"
* - Data type:
{@link RestResourceResolver} | Class<? extends {@link RestResourceResolver}>
* - Default: {@link BasicRestResourceResolver}
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#resourceResolver()}
*
* - Methods:
*
* - {@link RestContextBuilder#resourceResolver(Class)}
*
- {@link RestContextBuilder#resourceResolver(RestResourceResolver)}
*
*
*
* Description:
*
* The resolver used for resolving instances of child resources.
*
*
* Can be used to provide customized resolution of REST resource class instances (e.g. resources retrieve from Spring).
*
*
Example:
*
* // Our custom resource resolver.
* public class MyResourceResolver extends RestResourceResolverSimple {
*
* @Override
* public Object resolve(Class<?> resourceType, RestContextBuilder builder) throws Exception {
* Object resource = findOurResourceSomehow (resourceType);
*
* // If we can't resolve it, use default resolution.
* if (resource == null )
* resource = super .resolve(resourceType, builder);
*
* return resource;
* }
* }
*
* // Option #1 - Defined via annotation.
* @RestResource (resourceResolver=MyResourceResolver.class )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.resourceResolver(MyResourceResolver.class );
*
* // Same, but using property.
* builder.set(REST_resourceResolver , MyResourceResolver.class );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.resourceResolver(MyResourceResolver.class );
* }
* }
*
*
* Notes:
*
* -
* Unless overridden, resource resolvers are inherited from ascendant resources.
*
-
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(RestContext)
* public T()
*
* -
* Inner classes of the REST resource class are allowed.
*
*
* See Also:
*
* - {@doc juneau-rest-server.Instantiation.ResourceResolvers}
*
- {@doc juneau-rest-server.Injection}
*
*/
public static final String REST_resourceResolver = PREFIX + "resourceResolver.o";
/**
* Configuration property: Response handlers.
*
* Property:
*
* - Name:
"RestContext.responseHandlers.lo"
* - Data type:
List<{@link ResponseHandler} | Class<? extends {@link ResponseHandler}>>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#responseHandlers()}
*
* - Methods:
*
* - {@link RestContextBuilder#responseHandlers(Class...)}
*
- {@link RestContextBuilder#responseHandlers(ResponseHandler...)}
*
*
*
* Description:
*
* Specifies a list of {@link ResponseHandler} classes that know how to convert POJOs returned by REST methods or
* set via {@link RestResponse#setOutput(Object)} into appropriate HTTP responses.
*
*
* By default, the following response handlers are provided out-of-the-box:
*
* - {@link ReaderHandler} - {@link Reader} objects.
*
- {@link InputStreamHandler} - {@link InputStream} objects.
*
- {@link DefaultHandler} - All other POJOs.
*
*
* Example:
*
* // Our custom response handler for MySpecialObject objects.
* public class MyResponseHandler implements ResponseHandler {
*
* @Override
* public boolean handle(RestRequest req, RestResponse res, Object output) throws IOException, RestException {
* if (output instanceof MySpecialObject) {
* try (Writer w = res.getNegotiatedWriter()) {
* //Pipe it to the writer ourselves.
* }
* return true ; // We handled it.
* }
* return false ; // We didn't handle it.
* }
* }
*
* // Option #1 - Defined via annotation.
* @RestResource (responseHandlers=MyResponseHandler.class )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.responseHandlers(MyResponseHandler.class );
*
* // Same, but using property.
* builder.addTo(REST_responseHandlers , MyResponseHandler.class );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.responseHandlers(MyResponseHandler.class );
* }
*
* @RestMethod (...)
* public Object myMethod() {
* // Return a special object for our handler.
* return new MySpecialObject();
* }
* }
*
*
* Notes:
*
* -
* Response handlers resolvers are always inherited from ascendant resources.
*
-
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(RestContext)
* public T()
*
* -
* Inner classes of the REST resource class are allowed.
*
*/
public static final String REST_responseHandlers = PREFIX + "responseHandlers.lo";
/**
* Configuration property: Serializers.
*
* Property:
*
* - Name:
"RestContext.serializers.lo"
* - Data type:
List<{@link Serializer} | Class<? extends {@link Serializer}>>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#serializers()}
*
- {@link RestMethod#serializers()}
*
* - Methods:
*
* - {@link RestContextBuilder#serializers(Object...)}
*
- {@link RestContextBuilder#serializers(Class...)}
*
- {@link RestContextBuilder#serializers(boolean,Object...)}
*
*
*
* Description:
*
* Adds class-level serializers to this resource.
*
*
* Serializer are used to convert POJOs to HTTP response bodies.
*
Any of the Juneau framework serializers can be used in this setting.
*
The serializer selected is based on the request Accept
header matched against the values returned by the following method
* using a best-match algorithm:
*
* - {@link Serializer#getMediaTypeRanges()}
*
*
* Example:
*
* // Option #1 - Defined via annotation.
* @RestResource (serializers={JsonSerializer.class , XmlSerializer.class })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.serializers(JsonSerializer.class , XmlSerializer.class );
*
* // Same, but use pre-instantiated parsers.
* builder.serializers(JsonSerializer.DEFAULT , XmlSerializer.DEFAULT );
*
* // Same, but using property.
* builder.set(REST_serializers , JsonSerializer.class , XmlSerializer.class );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.serializers(JsonSerializer.class , XmlSerializer.class );
* }
*
* // Override at the method level.
* @RestMethod (serializers={HtmlSerializer.class })
* public MyPojo myMethod() {
* // Return a POJO to be serialized.
* return new MyPojo();
* }
* }
*
*
* Notes:
*
* -
* When defined as a class, properties/transforms defined on the resource/method are inherited.
*
-
* When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
*
-
* Typically, you'll want your resource to extend directly from {@link BasicRestServlet} which comes
* preconfigured with the following serializers:
*
* - {@link HtmlDocSerializer}
*
- {@link HtmlStrippedDocSerializer}
*
- {@link HtmlSchemaDocSerializer}
*
- {@link JsonSerializer}
*
- {@link SimpleJsonSerializer}
*
- {@link JsonSchemaSerializer}
*
- {@link XmlDocSerializer}
*
- {@link XmlSchemaDocSerializer}
*
- {@link UonSerializer}
*
- {@link UrlEncodingSerializer}
*
- {@link MsgPackSerializer}
*
- {@link SoapXmlSerializer}
*
- {@link PlainTextSerializer}
*
*
*
* See Also:
*
* - {@doc juneau-rest-server.Serializers}
*
*
*/
public static final String REST_serializers = PREFIX + "serializers.lo";
/**
* Configuration property: Static file response headers.
*
*
Property:
*
* - Name:
"RestContext.staticFileResponseHeaders.omo"
* - Data type:
Map<String,String>
* - Default:
{'Cache-Control' : 'max-age=86400, public }
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#staticFileResponseHeaders()}
*
* - Methods:
*
* - {@link RestContextBuilder#staticFileResponseHeaders(boolean,Map)}
*
- {@link RestContextBuilder#staticFileResponseHeaders(String...)}
*
- {@link RestContextBuilder#staticFileResponseHeader(String,String)}
*
*
*
* Description:
*
* Used to customize the headers on responses returned for statically-served files.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (
* staticFileResponseHeaders={
* "Cache-Control: $C{REST/cacheControl,nocache}" ,
* "My-Header: $C{REST/myHeaderValue}"
* }
* )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder
* .staticFileResponseHeader("Cache-Control" , "nocache" );
* .staticFileResponseHeaders("My-Header: foo" );
*
* // Same, but using property.
* builder
* .addTo(REST_staticFileResponseHeaders , "Cache-Control" , "nocache" );
* .addTo(REST_staticFileResponseHeaders , "My-Header" , "foo" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.staticFileResponseHeader("Cache-Control" , "nocache" );
* }
* }
*
*
* See Also:
*
* - {@link #REST_staticFiles} for information about statically-served files.
*
*/
public static final String REST_staticFileResponseHeaders = PREFIX + "staticFileResponseHeaders.omo";
/**
* Configuration property: Static file mappings.
*
* Property:
*
* - Name:
"RestContext.staticFiles.lo"
* - Data type:
List<StaticFileMapping>
* - Default:
null
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#staticFiles()}
*
* - Methods:
*
* - {@link RestContextBuilder#staticFiles(String)},
*
- {@link RestContextBuilder#staticFiles(Class,String)}
*
- {@link RestContextBuilder#staticFiles(String,String)}
*
- {@link RestContextBuilder#staticFiles(Class,String,String)}
*
- {@link RestContextBuilder#staticFiles(StaticFileMapping...)}
*
*
*
* Description:
*
* Used to define paths and locations of statically-served files such as images or HTML documents
* from the classpath or file system.
*
*
* An example where this class is used is in the {@link RestResource#staticFiles} annotation:
*
* package com.foo.mypackage;
*
* @RestResource (
* path="/myresource" ,
* staticFiles={
* "htdocs:docs" ,
* "styles:styles"
* }
* )
* public class MyResource extends BasicRestServlet {...}
*
*
*
* In the example above, given a GET request to the following URL...
*
* /myresource/htdocs/foobar.html
*
*
...the servlet will attempt to find the foobar.html
file in the following ordered locations:
*
* com.foo.mypackage.docs
package.
* [working-dir]/docs
directory.
*
*
* See Also:
*
* - {@link #REST_classpathResourceFinder} for configuring how classpath resources are located and retrieved.
*
- {@link #REST_mimeTypes} for configuring the media types based on file extension.
*
- {@link #REST_staticFileResponseHeaders} for configuring response headers on statically served files.
*
- {@link #REST_useClasspathResourceCaching} for configuring static file caching.
*
- {@link RestContext#getClasspathResource(String,Locale)} for retrieving static files.
*
*
* Notes:
*
* -
* Mappings are cumulative from super classes.
*
-
* Child resources can override mappings made on parent class resources.
*
*/
public static final String REST_staticFiles = PREFIX + "staticFiles.lo";
/**
* Configuration property: Supported accept media types.
*
* Property:
*
* - Name:
"RestContext.produces.ls"
* - Data type:
List<String>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#produces()}
*
- {@link RestMethod#produces()}
*
* - Methods:
*
* - {@link RestContextBuilder#produces(boolean,String...)}
*
- {@link RestContextBuilder#produces(boolean,MediaType...)}
*
*
*
* Description:
*
* Overrides the media types inferred from the serializers that identify what media types can be produced by the resource.
*
An example where this might be useful if you have serializers registered that handle media types that you
* don't want exposed in the Swagger documentation.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (produces={"$C{REST/supportedProduces,application/json}" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.produces(false , "application/json" )
*
* // Same, but using property.
* builder.set(REST_produces , "application/json" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.produces(false , "application/json" );
* }
* }
*
*
*
* This affects the returned values from the following:
*
* - {@link RestContext#getProduces() RestContext.getProduces()}
*
- {@link RestRequest#getProduces()}
*
- {@link RestInfoProvider#getSwagger(RestRequest)} - Affects produces field.
*
*/
public static final String REST_produces = PREFIX + "produces.ls";
/**
* Configuration property: Supported content media types.
*
* Property:
*
* - Name:
"RestContext.consumes.ls"
* - Data type:
List<String>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link RestResource#consumes()}
*
- {@link RestMethod#consumes()}
*
* - Methods:
*
* - {@link RestContextBuilder#consumes(boolean,String...)}
*
- {@link RestContextBuilder#consumes(boolean,MediaType...)}
*
*
*
* Description:
*
* Overrides the media types inferred from the parsers that identify what media types can be consumed by the resource.
*
An example where this might be useful if you have parsers registered that handle media types that you
* don't want exposed in the Swagger documentation.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (consumes={"$C{REST/supportedConsumes,application/json}" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.consumes(false , "application/json" )
*
* // Same, but using property.
* builder.set(REST_consumes , "application/json" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.consumes(false , "application/json" );
* }
* }
*
*
*
* This affects the returned values from the following:
*
* - {@link RestContext#getConsumes() RestContext.getConsumes()}
*
- {@link RestRequest#getConsumes()}
*
- {@link RestInfoProvider#getSwagger(RestRequest)} - Affects consumes field.
*
*/
public static final String REST_consumes = PREFIX + "consumes.ls";
/**
* Configuration property: Use classpath resource caching.
*
* Property:
*
* - Name:
"RestContext.useClasspathResourceCaching.b"
* - Data type:
Boolean
* - Default:
true
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#useClasspathResourceCaching()}
*
* - Methods:
*
* - {@link RestContextBuilder#useClasspathResourceCaching(boolean)}
*
*
*
* Description:
*
* When enabled, resources retrieved via {@link RestContext#getClasspathResource(String, Locale)} (and related
* methods) will be cached in memory to speed subsequent lookups.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (useClasspathResourceCaching="$C{REST/useClasspathResourceCaching,false}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.useClasspathResourceCaching(false )
*
* // Same, but using property.
* builder.set(REST_useClasspathResourceCaching , false );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.useClasspathResourceCaching(false )
* }
* }
*
*
* See Also:
*
* - {@link #REST_staticFiles} for information about static files.
*
*/
public static final String REST_useClasspathResourceCaching = PREFIX + "useClasspathResourceCaching.b";
/**
* Configuration property: Use stack trace hashes.
*
* Property:
*
* - Name:
"RestContext.useStackTraceHashes.b"
* - Data type:
Boolean
* - Default:
true
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#useStackTraceHashes()}
*
* - Methods:
*
* - {@link RestContextBuilder#useStackTraceHashes(boolean)}
*
*
*
* Description:
*
* When enabled, the number of times an exception has occurred will be tracked based on stack trace hashsums.
*
*
* Affects the following methods:
*
* - {@link RestContext#getStackTraceOccurrence(Throwable) RestContext.getStackTraceOccurrance(Throwable)}
*
- {@link RestCallHandler#handleError(HttpServletRequest, HttpServletResponse, Throwable)}
*
- {@link RestException#getOccurrence()} - Returns the number of times this exception occurred.
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (useStackTraceHashes="$C{REST/useStackTraceHashes,false}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.useStackTraceHashes(false )
*
* // Same, but using property.
* builder.set(REST_useStackTraceHashes , false );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.useStackTraceHashes(false )
* }
* }
*
*/
public static final String REST_useStackTraceHashes = PREFIX + "useStackTraceHashes.b";
/**
* Configuration property: Resource URI authority path.
*
* Property:
*
* - Name:
"RestContext.uriAuthority.s"
* - Data type:
String
* - Default:
null
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#uriAuthority()}
*
* - Methods:
*
* - {@link RestContextBuilder#uriAuthority(String)}
*
*
*
* Description:
*
* Overrides the authority path value for this resource and any child resources.
*
*
* Affects the following methods:
*
* - {@link RestRequest#getAuthorityPath()}
*
*
*
* If you do not specify the authority, it is automatically calculated via the following:
*
*
* String scheme = request.getScheme();
* int port = request.getServerPort();
* StringBuilder sb = new StringBuilder(request.getScheme()).append("://" ).append(request.getServerName());
* if (! (port == 80 && "http" .equals(scheme) || port == 443 && "https" .equals(scheme)))
* sb.append(':' ).append(port);
* authorityPath = sb.toString();
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (
* path="/servlet" ,
* uriAuthority="$C{REST/authorityPathOverride,http://localhost:10000}"
* )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.uriAuthority("http://localhost:10000" );
*
* // Same, but using property.
* builder.set(REST_uriAuthority , "http://localhost:10000" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.uriAuthority("http://localhost:10000" );
* }
* }
*
*/
public static final String REST_uriAuthority = PREFIX + "uriAuthority.s";
/**
* Configuration property: Resource URI context path.
*
* Property:
*
* - Name:
"RestContext.uriContext.s"
* - Data type:
String
* - Default:
null
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#uriContext()}
*
* - Methods:
*
* - {@link RestContextBuilder#uriContext(String)}
*
*
*
* Description:
*
* Overrides the context path value for this resource and any child resources.
*
*
* This setting is useful if you want to use "context:/child/path" URLs in child resource POJOs but
* the context path is not actually specified on the servlet container.
*
*
* Affects the following methods:
*
* - {@link RestRequest#getContextPath()} - Returns the overridden context path for the resource.
*
- {@link RestRequest#getServletPath()} - Includes the overridden context path for the resource.
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (
* path="/servlet" ,
* uriContext="$C{REST/contextPathOverride,/foo}"
* )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.uriContext("/foo" );
*
* // Same, but using property.
* builder.set(REST_uriContext , "/foo" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.uriContext("/foo" );
* }
* }
*
*/
public static final String REST_uriContext = PREFIX + "uriContext.s";
/**
* Configuration property: URI resolution relativity.
*
* Property:
*
* - Name:
"RestContext.uriRelativity.s"
* - Data type:
String
* - Default:
"RESOURCE"
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#uriRelativity()}
*
* - Methods:
*
* - {@link RestContextBuilder#uriRelativity(String)}
*
*
*
* Description:
*
* Specifies how relative URIs should be interpreted by serializers.
*
*
* See {@link UriResolution} for possible values.
*
*
* Affects the following methods:
*
* - {@link RestRequest#getUriResolver()}
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (
* path="/servlet" ,
* uriRelativity="$C{REST/uriRelativity,PATH_INFO}"
* )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.uriRelativity("PATH_INFO" );
*
* // Same, but using property.
* builder.set(REST_uriRelativity , "PATH_INFO" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.uriRelativity("PATH_INFO" );
* }
* }
*
*/
public static final String REST_uriRelativity = PREFIX + "uriRelativity.s";
/**
* Configuration property: URI resolution.
*
* Property:
*
* - Name:
"RestContext.uriResolution.s"
* - Data type:
String
* - Default:
"ROOT_RELATIVE"
* - Session property:
false
* - Annotations:
*
* - {@link RestResource#uriResolution()}
*
* - Methods:
*
* - {@link RestContextBuilder#uriResolution(String)}
*
*
*
* Description:
*
* Specifies how relative URIs should be interpreted by serializers.
*
*
* See {@link UriResolution} for possible values.
*
*
* Affects the following methods:
*
* - {@link RestRequest#getUriResolver()}
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @RestResource (
* path="/servlet" ,
* uriResolution="$C{REST/uriResolution,ABSOLUTE}"
* )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContextBuilder builder) throws Exception {
*
* // Using method on builder.
* builder.uriResolution("ABSOLUTE" );
*
* // Same, but using property.
* builder.set(REST_uriResolution , "ABSOLUTE" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestHook (INIT )
* public void init(RestContextBuilder builder) throws Exception {
* builder.uriResolution("ABSOLUTE" );
* }
* }
*
*/
public static final String REST_uriResolution = PREFIX + "uriResolution.s";
/**
* Configuration property: HTML Widgets.
*
* Property:
*
* - Name:
"RestContext.widgets.lo"
* - Data type:
List<{@link Widget} | Class<? extends {@link Widget}>>
* - Default: empty list
*
- Session property:
false
* - Annotations:
*
* - {@link HtmlDoc#widgets()}
*
* - Methods:
*
* - {@link RestContextBuilder#widgets(Class...)}
*
- {@link RestContextBuilder#widgets(Widget...)}
*
- {@link RestContextBuilder#widgets(boolean,Widget...)}
*
*
*
* Description:
*
* Defines widgets that can be used in conjunction with string variables of the form "$W{name}" to quickly
* generate arbitrary replacement text.
*
* Widgets resolve the following variables:
*
* "$W{name}" - Contents returned by {@link Widget#getHtml(RestRequest)}.
* "$W{name.script}" - Contents returned by {@link Widget#getScript(RestRequest)}.
*
The script contents are automatically inserted into the <head/script> section
* in the HTML page.
* "$W{name.style}" - Contents returned by {@link Widget#getStyle(RestRequest)}.
*
The styles contents are automatically inserted into the <head/style> section
* in the HTML page.
*
*
*
* The following examples shows how to associate a widget with a REST method and then have it rendered in the links
* and aside section of the page:
*
*
* @RestMethod (
* widgets={
* MyWidget.class
* }
* htmldoc=@HtmlDoc (
* navlinks={
* "$W{MyWidget}"
* },
* aside={
* "Check out this widget: $W{MyWidget}"
* }
* )
* )
*
*
* Notes:
*
* -
* Widgets are inherited from super classes, but can be overridden by reusing the widget name.
*
*
* See Also:
*
* - {@doc juneau-rest-server.HtmlDocAnnotation.Widgets}
*
*/
public static final String REST_widgets = PREFIX + "widgets.lo";
//-------------------------------------------------------------------------------------------------------------------
// Instance
//-------------------------------------------------------------------------------------------------------------------
private final Object resource;
final RestContextBuilder builder;
private final boolean
allowHeaderParams,
allowBodyParam,
renderResponseStackTraces,
useStackTraceHashes,
useClasspathResourceCaching,
debug;
private final String
defaultCharset,
clientVersionHeader,
uriAuthority,
uriContext;
private final long
maxInput;
final String fullPath;
private final Map widgets;
private final Set allowedMethodParams;
private final RestContextProperties properties;
private final Map,RestMethodParam> paramResolvers;
private final SerializerGroup serializers;
private final ParserGroup parsers;
private final HttpPartSerializer partSerializer;
private final HttpPartParser partParser;
private final EncoderGroup encoders;
private final List
consumes,
produces;
private final Map
defaultRequestHeaders,
defaultResponseHeaders,
staticFileResponseHeaders;
private final BeanContext beanContext;
private final RestConverter[] converters;
private final RestGuard[] guards;
private final ResponseHandler[] responseHandlers;
private final MimetypesFileTypeMap mimetypesFileTypeMap;
private final StaticFileMapping[] staticFiles;
private final String[] staticFilesPaths;
private final MessageBundle msgs;
private final Config config;
private final VarResolver varResolver;
private final Map callRouters;
private final Map callMethods;
private final Map childResources;
private final RestLogger logger;
private final RestCallHandler callHandler;
private final RestInfoProvider infoProvider;
private final RestException initException;
private final RestContext parentContext;
private final RestResourceResolver resourceResolver;
private final UriResolution uriResolution;
private final UriRelativity uriRelativity;
// Lifecycle methods
private final Method[]
postInitMethods,
postInitChildFirstMethods,
preCallMethods,
postCallMethods,
startCallMethods,
endCallMethods,
destroyMethods;
private final RestMethodParam[][]
preCallMethodParams,
postCallMethodParams;
private final Class>[][]
postInitMethodParams,
postInitChildFirstMethodParams,
startCallMethodParams,
endCallMethodParams,
destroyMethodParams;
// In-memory cache of images and stylesheets in the org.apache.juneau.rest.htdocs package.
private final Map staticFilesCache = new ConcurrentHashMap<>();
private final ClasspathResourceManager staticResourceManager;
private final ConcurrentHashMap stackTraceHashes = new ConcurrentHashMap<>();
/**
* Constructor.
*
* @param resource The resource annotated with @RestResource .
* @return A new builder object.
* @throws ServletException Something bad happened.
*/
public static RestContextBuilder create(Object resource) throws ServletException {
return new RestContextBuilder(null, resource.getClass(), null).init(resource);
}
/**
* Constructor.
*
* @param servletConfig The servlet config passed into the servlet by the servlet container.
* @param resourceClass The class annotated with @RestResource .
* @param parentContext The parent context, or null if there is no parent context.
* @return A new builder object.
* @throws ServletException Something bad happened.
*/
static RestContextBuilder create(ServletConfig servletConfig, Class> resourceClass, RestContext parentContext) throws ServletException {
return new RestContextBuilder(servletConfig, resourceClass, parentContext);
}
/**
* Constructor.
*
* @param builder The servlet configuration object.
* @throws Exception If any initialization problems were encountered.
*/
RestContext(RestContextBuilder builder) throws Exception {
super(builder.getPropertyStore());
RestException _initException = null;
try {
ServletContext servletContext = builder.servletContext;
this.resource = builder.resource;
this.builder = builder;
this.parentContext = builder.parentContext;
PropertyStore ps = getPropertyStore().builder().add(builder.properties).build();
Class> resourceClass = resource.getClass();
uriContext = nullIfEmpty(getStringProperty(REST_uriContext, null));
uriAuthority = nullIfEmpty(getStringProperty(REST_uriAuthority, null));
uriResolution = getProperty(REST_uriResolution, UriResolution.class, UriResolution.ROOT_RELATIVE);
uriRelativity = getProperty(REST_uriRelativity, UriRelativity.class, UriRelativity.RESOURCE);
allowHeaderParams = getBooleanProperty(REST_allowHeaderParams, true);
allowBodyParam = getBooleanProperty(REST_allowBodyParam, true);
allowedMethodParams = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(StringUtils.split(getStringProperty(REST_allowedMethodParams, "HEAD,OPTIONS")))));
renderResponseStackTraces = getBooleanProperty(REST_renderResponseStackTraces, false);
useStackTraceHashes = getBooleanProperty(REST_useStackTraceHashes, true);
debug = getBooleanProperty(REST_debug, super.isDebug());
defaultCharset = getStringProperty(REST_defaultCharset, "utf-8");
maxInput = getLongProperty(REST_maxInput, 100_000_000l);
clientVersionHeader = getStringProperty(REST_clientVersionHeader, "X-Client-Version");
converters = getInstanceArrayProperty(REST_converters, resource, RestConverter.class, new RestConverter[0], true, this);
guards = getInstanceArrayProperty(REST_guards, resource, RestGuard.class, new RestGuard[0], true, this);
responseHandlers = getInstanceArrayProperty(REST_responseHandlers, resource, ResponseHandler.class, new ResponseHandler[0], true, this);
Map,RestMethodParam> _paramResolvers = new HashMap<>();
for (RestMethodParam rp : getInstanceArrayProperty(REST_paramResolvers, RestMethodParam.class, new RestMethodParam[0], true, this))
_paramResolvers.put(rp.forClass(), rp);
paramResolvers = unmodifiableMap(_paramResolvers);
Map _defaultRequestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
_defaultRequestHeaders.putAll(getMapProperty(REST_defaultRequestHeaders, String.class));
defaultRequestHeaders = unmodifiableMap(new LinkedHashMap<>(_defaultRequestHeaders));
defaultResponseHeaders = getMapProperty(REST_defaultResponseHeaders, Object.class);
staticFileResponseHeaders = getMapProperty(REST_staticFileResponseHeaders, Object.class);
logger = getInstanceProperty(REST_logger, resource, RestLogger.class, NoOpRestLogger.class, true, this);
if (debug)
logger.setLevel(Level.FINE);
varResolver = builder.varResolverBuilder
.vars(
FileVar.class,
LocalizationVar.class,
RequestAttributeVar.class,
RequestFormDataVar.class,
RequestHeaderVar.class,
RequestPathVar.class,
RequestQueryVar.class,
RequestVar.class,
RestInfoVar.class,
SerializedRequestAttrVar.class,
ServletInitParamVar.class,
SwaggerVar.class,
UrlVar.class,
UrlEncodeVar.class,
WidgetVar.class
)
.build()
;
config = builder.config.resolving(this.varResolver.createSession());
properties = builder.properties;
serializers = SerializerGroup.create().append(getInstanceArrayProperty(REST_serializers, Serializer.class, new Serializer[0], true, resource, ps)).build();
parsers = ParserGroup.create().append(getInstanceArrayProperty(REST_parsers, Parser.class, new Parser[0], true, resource, ps)).build();
partSerializer = getInstanceProperty(REST_partSerializer, HttpPartSerializer.class, OpenApiSerializer.class, true, resource, ps);
partParser = getInstanceProperty(REST_partParser, HttpPartParser.class, OpenApiParser.class, true, resource, ps);
encoders = new EncoderGroupBuilder().append(getInstanceArrayProperty(REST_encoders, Encoder.class, new Encoder[0], true, resource, ps)).build();
beanContext = BeanContext.create().apply(ps).build();
mimetypesFileTypeMap = new ExtendedMimetypesFileTypeMap();
for (String mimeType : getArrayProperty(REST_mimeTypes, String.class))
mimetypesFileTypeMap.addMimeTypes(mimeType);
ClasspathResourceFinder rf = getInstanceProperty(REST_classpathResourceFinder, ClasspathResourceFinder.class, ClasspathResourceFinderBasic.class, true, this);
useClasspathResourceCaching = getProperty(REST_useClasspathResourceCaching, boolean.class, true);
staticResourceManager = new ClasspathResourceManager(resourceClass, rf, useClasspathResourceCaching);
consumes = getListProperty(REST_consumes, MediaType.class, parsers.getSupportedMediaTypes());
produces = getListProperty(REST_produces, MediaType.class, serializers.getSupportedMediaTypes());
staticFiles = ArrayUtils.reverse(getArrayProperty(REST_staticFiles, StaticFileMapping.class));
Set s = new TreeSet<>();
for (StaticFileMapping sfm : staticFiles)
s.add(sfm.path);
staticFilesPaths = s.toArray(new String[s.size()]);
MessageBundleLocation[] mbl = getInstanceArrayProperty(REST_messages, MessageBundleLocation.class, new MessageBundleLocation[0]);
if (mbl.length == 0)
msgs = new MessageBundle(resourceClass, "");
else {
msgs = new MessageBundle(mbl[0] != null ? mbl[0].baseClass : resourceClass, mbl[0].bundlePath);
for (int i = 1; i < mbl.length; i++)
msgs.addSearchPath(mbl[i] != null ? mbl[i].baseClass : resourceClass, mbl[i].bundlePath);
}
fullPath = (builder.parentContext == null ? "" : (builder.parentContext.fullPath + '/')) + builder.path;
this.childResources = Collections.synchronizedMap(new LinkedHashMap()); // Not unmodifiable on purpose so that children can be replaced.
Map _widgets = new LinkedHashMap<>();
for (Widget w : getInstanceArrayProperty(REST_widgets, resource, Widget.class, new Widget[0], true, ps))
_widgets.put(w.getName(), w);
this.widgets = unmodifiableMap(_widgets);
//----------------------------------------------------------------------------------------------------
// Initialize the child resources.
// Done after initializing fields above since we pass this object to the child resources.
//----------------------------------------------------------------------------------------------------
List methodsFound = new LinkedList<>(); // Temporary to help debug transient duplicate method issue.
Map routers = new LinkedHashMap<>();
Map _javaRestMethods = new LinkedHashMap<>();
Map
_startCallMethods = new LinkedHashMap<>(),
_preCallMethods = new LinkedHashMap<>(),
_postCallMethods = new LinkedHashMap<>(),
_endCallMethods = new LinkedHashMap<>(),
_postInitMethods = new LinkedHashMap<>(),
_postInitChildFirstMethods = new LinkedHashMap<>(),
_destroyMethods = new LinkedHashMap<>();
List
_preCallMethodParams = new ArrayList<>(),
_postCallMethodParams = new ArrayList<>();
List[]>
_startCallMethodParams = new ArrayList<>(),
_endCallMethodParams = new ArrayList<>(),
_postInitMethodParams = new ArrayList<>(),
_postInitChildFirstMethodParams = new ArrayList<>(),
_destroyMethodParams = new ArrayList<>();
for (java.lang.reflect.Method method : resourceClass.getMethods()) {
if (method.isAnnotationPresent(RestMethod.class)) {
RestMethod a = method.getAnnotation(RestMethod.class);
methodsFound.add(method.getName() + "," + a.name() + "," + a.path());
try {
if (! isPublic(method))
throw new RestServletException("@RestMethod method {0}.{1} must be defined as public.", resourceClass.getName(), method.getName());
RestJavaMethod sm = new RestJavaMethod(resource, method, this);
String httpMethod = sm.getHttpMethod();
// PROXY is a special case where a method returns an interface that we
// can perform REST calls against.
// We override the CallMethod.invoke() method to insert our logic.
if ("PROXY".equals(httpMethod)) {
final ClassMeta> interfaceClass = beanContext.getClassMeta(method.getGenericReturnType());
final RemoteInterfaceMeta rim = new RemoteInterfaceMeta(interfaceClass.getInnerClass(), null);
if (rim.getMethodsByPath().isEmpty())
throw new RestException(SC_INTERNAL_SERVER_ERROR, "Method {0} returns an interface {1} that doesn't define any remote methods.", getMethodSignature(method), interfaceClass.getReadableName());
sm = new RestJavaMethod(resource, method, this) {
@Override
int invoke(String pathInfo, RestRequest req, RestResponse res) throws Throwable {
int rc = super.invoke(pathInfo, req, res);
if (rc != SC_OK)
return rc;
final Object o = res.getOutput();
if ("GET".equals(req.getMethod())) {
res.setOutput(rim.getMethodsByPath().keySet());
return SC_OK;
} else if ("POST".equals(req.getMethod())) {
if (pathInfo.indexOf('/') != -1)
pathInfo = pathInfo.substring(pathInfo.lastIndexOf('/')+1);
pathInfo = urlDecode(pathInfo);
RemoteInterfaceMethod rmm = rim.getMethodMetaByPath(pathInfo);
if (rmm != null) {
Method m = rmm.getJavaMethod();
try {
// Parse the args and invoke the method.
Parser p = req.getBody().getParser();
Object[] args = null;
if (m.getGenericParameterTypes().length == 0)
args = new Object[0];
else {
try (Closeable in = p.isReaderParser() ? req.getReader() : req.getInputStream()) {
args = p.parseArgs(in, m.getGenericParameterTypes());
}
}
Object output = m.invoke(o, args);
res.setOutput(output);
return SC_OK;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
}
return SC_NOT_FOUND;
}
};
_javaRestMethods.put(method.getName(), sm);
addToRouter(routers, "GET", sm);
addToRouter(routers, "POST", sm);
} else {
_javaRestMethods.put(method.getName(), sm);
addToRouter(routers, httpMethod, sm);
}
} catch (Throwable e) {
throw new RestServletException("Problem occurred trying to serialize methods on class {0}, methods={1}", resourceClass.getName(), SimpleJsonSerializer.DEFAULT.serialize(methodsFound)).initCause(e);
}
}
}
for (Method m : ClassUtils.getAllMethods(resourceClass, true)) {
if (ClassUtils.isPublic(m) && m.isAnnotationPresent(RestHook.class)) {
HookEvent he = m.getAnnotation(RestHook.class).value();
String sig = ClassUtils.getMethodSignature(m);
switch(he) {
case PRE_CALL: {
if (! _preCallMethods.containsKey(sig)) {
setAccessible(m, false);
_preCallMethods.put(sig, m);
_preCallMethodParams.add(findParams(m, null, true));
}
break;
}
case POST_CALL: {
if (! _postCallMethods.containsKey(sig)) {
setAccessible(m, false);
_postCallMethods.put(sig, m);
_postCallMethodParams.add(findParams(m, null, true));
}
break;
}
case START_CALL: {
if (! _startCallMethods.containsKey(sig)) {
setAccessible(m, false);
_startCallMethods.put(sig, m);
_startCallMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, HttpServletRequest.class, HttpServletResponse.class);
}
break;
}
case END_CALL: {
if (! _endCallMethods.containsKey(sig)) {
setAccessible(m, false);
_endCallMethods.put(sig, m);
_endCallMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, HttpServletRequest.class, HttpServletResponse.class);
}
break;
}
case POST_INIT: {
if (! _postInitMethods.containsKey(sig)) {
setAccessible(m, false);
_postInitMethods.put(sig, m);
_postInitMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, RestContext.class);
}
break;
}
case POST_INIT_CHILD_FIRST: {
if (! _postInitChildFirstMethods.containsKey(sig)) {
setAccessible(m, false);
_postInitChildFirstMethods.put(sig, m);
_postInitChildFirstMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, RestContext.class);
}
break;
}
case DESTROY: {
if (! _destroyMethods.containsKey(sig)) {
setAccessible(m, false);
_destroyMethods.put(sig, m);
_destroyMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, RestContext.class);
}
break;
}
default: // Ignore INIT
}
}
}
this.callMethods = unmodifiableMap(_javaRestMethods);
this.preCallMethods = _preCallMethods.values().toArray(new Method[_preCallMethods.size()]);
this.postCallMethods = _postCallMethods.values().toArray(new Method[_postCallMethods.size()]);
this.startCallMethods = _startCallMethods.values().toArray(new Method[_startCallMethods.size()]);
this.endCallMethods = _endCallMethods.values().toArray(new Method[_endCallMethods.size()]);
this.postInitMethods = _postInitMethods.values().toArray(new Method[_postInitMethods.size()]);
this.postInitChildFirstMethods = _postInitChildFirstMethods.values().toArray(new Method[_postInitChildFirstMethods.size()]);
this.destroyMethods = _destroyMethods.values().toArray(new Method[_destroyMethods.size()]);
this.preCallMethodParams = _preCallMethodParams.toArray(new RestMethodParam[_preCallMethodParams.size()][]);
this.postCallMethodParams = _postCallMethodParams.toArray(new RestMethodParam[_postCallMethodParams.size()][]);
this.startCallMethodParams = _startCallMethodParams.toArray(new Class[_startCallMethodParams.size()][]);
this.endCallMethodParams = _endCallMethodParams.toArray(new Class[_endCallMethodParams.size()][]);
this.postInitMethodParams = _postInitMethodParams.toArray(new Class[_postInitMethodParams.size()][]);
this.postInitChildFirstMethodParams = _postInitChildFirstMethodParams.toArray(new Class[_postInitChildFirstMethodParams.size()][]);
this.destroyMethodParams = _destroyMethodParams.toArray(new Class[_destroyMethodParams.size()][]);
Map _callRouters = new LinkedHashMap<>();
for (RestCallRouter.Builder crb : routers.values())
_callRouters.put(crb.getHttpMethodName(), crb.build());
this.callRouters = unmodifiableMap(_callRouters);
// Initialize our child resources.
resourceResolver = getInstanceProperty(REST_resourceResolver, resource, RestResourceResolver.class, parentContext == null ? BasicRestResourceResolver.class : parentContext.resourceResolver, true, this);
for (Object o : getArrayProperty(REST_children, Object.class)) {
String path = null;
Object r = null;
if (o instanceof RestChild) {
RestChild rc = (RestChild)o;
path = rc.path;
r = rc.resource;
} else if (o instanceof Class>) {
Class> c = (Class>)o;
// Don't allow specifying yourself as a child. Causes an infinite loop.
if (c == builder.resourceClass)
continue;
r = c;
} else {
r = o;
}
RestContextBuilder childBuilder = null;
if (o instanceof Class) {
Class> oc = (Class>)o;
childBuilder = RestContext.create(builder.inner, oc, this);
r = resourceResolver.resolve(resource, oc, childBuilder);
} else {
r = o;
childBuilder = RestContext.create(builder.inner, o.getClass(), this);
}
childBuilder.init(r);
if (r instanceof RestServlet)
((RestServlet)r).innerInit(childBuilder);
childBuilder.servletContext(servletContext);
RestContext rc2 = childBuilder.build();
if (r instanceof RestServlet)
((RestServlet)r).setContext(rc2);
path = childBuilder.path;
childResources.put(path, rc2);
}
callHandler = getInstanceProperty(REST_callHandler, resource, RestCallHandler.class, BasicRestCallHandler.class, true, this);
infoProvider = getInstanceProperty(REST_infoProvider, resource, RestInfoProvider.class, BasicRestInfoProvider.class, true, this);
} catch (RestException e) {
_initException = e;
throw e;
} catch (Exception e) {
_initException = new RestException(e, SC_INTERNAL_SERVER_ERROR);
throw e;
} finally {
initException = _initException;
}
}
private static void addToRouter(Map routers, String httpMethodName, RestJavaMethod cm) throws RestServletException {
if (! routers.containsKey(httpMethodName))
routers.put(httpMethodName, new RestCallRouter.Builder(httpMethodName));
routers.get(httpMethodName).add(cm);
}
/**
* Returns the resource resolver associated with this context.
*
*
* The resource resolver is used for instantiating child resource classes.
*
*
See Also:
*
* - {@link #REST_resourceResolver}
*
*
* @return The resource resolver associated with this context.
*/
protected RestResourceResolver getResourceResolver() {
return resourceResolver;
}
/**
* Returns the variable resolver for this servlet.
*
*
* Variable resolvers are used to replace variables in property values.
* They can be nested arbitrarily deep.
* They can also return values that themselves contain other variables.
*
*
Example:
*
* @RestResource (
* messages="nls/Messages" ,
* properties={
* @Property (name="title" ,value="$L{title}" ), // Localized variable in Messages.properties
* @Property (name="javaVendor" ,value="$S{java.vendor,Oracle}" ), // System property with default value
* @Property (name="foo" ,value="bar" ),
* @Property (name="bar" ,value="baz" ),
* @Property (name="v1" ,value="$R{foo}" ), // Request variable. value="bar"
* @Property (name="v1" ,value="$R{foo,bar}" ), // Request variable. value="bar"
* }
* )
* public class MyRestResource extends BasicRestServlet {
*
*
*
* A typical usage pattern involves using variables inside the {@link HtmlDoc @HtmlDoc} annotation:
*
* @RestMethod (
* name=GET , path="/{name}/*" ,
* htmldoc=@HtmlDoc(
* navlinks={
* "up: $R{requestParentURI}" ,
* "options: servlet:/?method=OPTIONS" ,
* "editLevel: servlet:/editLevel?logger=$A{attribute.name, OFF}"
* }
* header={
* "<h1>$L{MyLocalizedPageTitle}</h1>"
* },
* aside={
* "$F{resources/AsideText.html}"
* }
* )
* )
* public LoggerEntry getLogger(RestRequest req, @Path String name) throws Exception {
*
*
* See Also:
*
* - {@link org.apache.juneau.rest.RestContextBuilder#vars(Class...)} - For adding custom vars.
*
- {@doc juneau-rest-server.SvlVariables}
*
- {@doc DefaultRestSvlVariables}
*
*
* @return The var resolver in use by this resource.
*/
public VarResolver getVarResolver() {
return varResolver;
}
/**
* Returns the config file associated with this servlet.
*
*
* The config file is identified via one of the following:
*
* - {@link RestResource#config()}
*
- {@link RestContextBuilder#config(Config)}
*
*
* @return
* The resolving config file associated with this servlet.
*
Never null .
*/
public Config getConfig() {
return config;
}
/**
* Resolve a static resource file.
*
*
* The location of static resources are defined via:
*
* - {@link RestContext#REST_staticFiles RestContext.REST_staticFiles}
*
*
* @param pathInfo The unencoded path info.
* @return The wrapped resource, never null .
* @throws NotFound Invalid path.
* @throws IOException
*/
protected StaticFile resolveStaticFile(String pathInfo) throws NotFound, IOException {
if (! staticFilesCache.containsKey(pathInfo)) {
String p = urlDecode(trimSlashes(pathInfo));
if (p.indexOf("..") != -1)
throw new NotFound("Invalid path");
StreamResource sr = null;
for (StaticFileMapping sfm : staticFiles) {
String path = sfm.path;
if (p.startsWith(path)) {
String remainder = (p.equals(path) ? "" : p.substring(path.length()));
if (remainder.isEmpty() || remainder.startsWith("/")) {
String p2 = sfm.location + remainder;
try (InputStream is = getClasspathResource(sfm.resourceClass, p2, null)) {
if (is != null) {
int i = p2.lastIndexOf('/');
String name = (i == -1 ? p2 : p2.substring(i+1));
String mediaType = mimetypesFileTypeMap.getContentType(name);
Map responseHeaders = sfm.responseHeaders != null ? sfm.responseHeaders : staticFileResponseHeaders;
sr = new StreamResource(MediaType.forString(mediaType), responseHeaders, is);
break;
}
}
}
}
}
StaticFile sf = new StaticFile(sr);
if (useClasspathResourceCaching) {
if (staticFilesCache.size() > 100)
staticFilesCache.clear();
staticFilesCache.put(pathInfo, sf);
}
return sf;
}
return staticFilesCache.get(pathInfo);
}
/**
* A cached static file instance.
*/
protected class StaticFile {
StreamResource resource;
ResponseBeanMeta meta;
/**
* Constructor.
*
* @param resource
*/
protected StaticFile(StreamResource resource) {
this.resource = resource;
this.meta = resource == null ? null : ResponseBeanMeta.create(resource.getClass(), getPropertyStore());
}
}
/**
* Same as {@link Class#getResourceAsStream(String)} except if it doesn't find the resource on this class, searches
* up the parent hierarchy chain.
*
*
* If the resource cannot be found in the classpath, then an attempt is made to look in the JVM working directory.
*
*
* If the locale
is specified, then we look for resources whose name matches that locale.
*
For example, if looking for the resource "MyResource.txt" for the Japanese locale, we will look for
* files in the following order:
*
* "MyResource_ja_JP.txt"
* "MyResource_ja.txt"
* "MyResource.txt"
*
*
* Example:
*
* // A rest method that (unsafely!) returns the contents of a localized file
* // from the classpath.
* @RestMethod (path="/foo" )
* public Object myMethod(RestRequest req, @Query ("file" ) String file) {
* return getContext().getClasspathResource(file, req.getLocale());
* }
*
*
* See Also:
*
* - {@link #REST_classpathResourceFinder}
*
*
* @param name The resource name.
* @param locale
* Optional locale.
*
If null , won't look for localized file names.
* @return An input stream of the resource, or null if the resource could not be found.
* @throws IOException
*/
public InputStream getClasspathResource(String name, Locale locale) throws IOException {
return staticResourceManager.getStream(name, locale);
}
/**
* Same as {@link #getClasspathResource(String, Locale)}, but allows you to override the class used for looking
* up the classpath resource.
*
* Example:
*
* // A rest method that (unsafely!) returns the contents of a localized file
* // from the classpath.
* @RestMethod (path="/foo" )
* public Object myMethod(RestRequest req, @Query ("file" ) String file) {
* return getContext().getClasspathResource(SomeOtherClass.class , file, req.getLocale());
* }
*
*
* See Also:
*
* - {@link #REST_classpathResourceFinder}
*
*
* @param baseClass
* Overrides the default class to use for retrieving the classpath resource.
*
If null , uses the REST resource class.
* @param name The resource name.
* @param locale
* Optional locale.
*
If null , won't look for localized file names.
* @return An input stream of the resource, or null if the resource could not be found.
* @throws IOException
*/
public InputStream getClasspathResource(Class> baseClass, String name, Locale locale) throws IOException {
return staticResourceManager.getStream(baseClass, name, locale);
}
/**
* Reads the input stream from {@link #getClasspathResource(String, Locale)} into a String.
*
* Example:
*
* // A rest method that (unsafely!) returns the contents of a localized file
* // from the classpath.
* @RestMethod (path="/foo" )
* public String myMethod(RestRequest req, @Query ("file" ) String file) {
* return getContext().getClasspathResourceAsString(file, req.getLocale());
* }
*
*
* See Also:
*
* - {@link #REST_classpathResourceFinder}
*
*
* @param name The resource name.
* @param locale
* Optional locale.
*
If null , won't look for localized file names.
* @return The contents of the stream as a string, or null if the resource could not be found.
* @throws IOException If resource could not be found.
*/
public String getClasspathResourceAsString(String name, Locale locale) throws IOException {
return staticResourceManager.getString(name, locale);
}
/**
* Same as {@link #getClasspathResourceAsString(String, Locale)}, but allows you to override the class used for looking
* up the classpath resource.
*
* Example:
*
* // A rest method that (unsafely!) returns the contents of a localized file
* // from the classpath.
* @RestMethod (path="/foo" )
* public String myMethod(RestRequest req, @Query ("file" ) String file) {
* return getContext().getClasspathResourceAsString(SomeOtherClass.class , file, req.getLocale());
* }
*
*
* See Also:
*
* - {@link #REST_classpathResourceFinder}
*
*
* @param baseClass
* Overrides the default class to use for retrieving the classpath resource.
*
If null , uses the REST resource class.
* @param name The resource name.
* @param locale
* Optional locale.
*
If null , won't look for localized file names.
* @return The contents of the stream as a string, or null if the resource could not be found.
* @throws IOException If resource could not be found.
*/
public String getClasspathResourceAsString(Class> baseClass, String name, Locale locale) throws IOException {
return staticResourceManager.getString(baseClass, name, locale);
}
/**
* Reads the input stream from {@link #getClasspathResource(String, Locale)} and parses it into a POJO using the parser
* matched by the specified media type.
*
*
* Useful if you want to load predefined POJOs from JSON files in your classpath.
*
*
Example:
*
* // A rest method that (unsafely!) returns the contents of a localized file
* // from the classpath parsed as an array of beans.
* @RestMethod (path="/foo" )
* public MyBean[] myMethod(RestRequest req, @Query ("file" ) String file) {
* return getContext().getClasspathResource(MyBean[].class , JSON , file, req.getLocale());
* }
*
*
* See Also:
*
* - {@link #REST_classpathResourceFinder}
*
*
* @param c The class type of the POJO to create.
* @param mediaType The media type of the data in the stream (e.g. "text/json" )
* @param name The resource name (e.g. "htdocs/styles.css").
* @param locale
* Optional locale.
*
If null , won't look for localized file names.
* @return The parsed resource, or null if the resource could not be found.
* @throws IOException
* @throws ServletException If the media type was unknown or the input could not be parsed into a POJO.
*/
public T getClasspathResource(Class c, MediaType mediaType, String name, Locale locale) throws IOException, ServletException {
return getClasspathResource(null, c, mediaType, name, locale);
}
/**
* Same as {@link #getClasspathResource(Class, MediaType, String, Locale)}, except overrides the class used
* for retrieving the classpath resource.
*
* See Also:
*
* - {@link #REST_classpathResourceFinder}
*
*
* Example:
*
* // A rest method that (unsafely!) returns the contents of a localized file
* // from the classpath parsed as an array of beans.
* @RestMethod (path="/foo" )
* public MyBean[] myMethod(RestRequest req, @Query ("file" ) String file) {
* return getContext().getClasspathResource(SomeOtherClass.class , MyBean[].class , JSON , file, req.getLocale());
* }
*
*
* @param baseClass
* Overrides the default class to use for retrieving the classpath resource.
*
If null, uses the REST resource class.
* @param c The class type of the POJO to create.
* @param mediaType The media type of the data in the stream (e.g. "text/json" )
* @param name The resource name (e.g. "htdocs/styles.css").
* @param locale
* Optional locale.
*
If null , won't look for localized file names.
* @return The parsed resource, or null if the resource could not be found.
* @throws IOException
* @throws ServletException If the media type was unknown or the input could not be parsed into a POJO.
*/
public T getClasspathResource(Class> baseClass, Class c, MediaType mediaType, String name, Locale locale) throws IOException, ServletException {
InputStream is = getClasspathResource(baseClass, name, locale);
if (is == null)
return null;
try {
Parser p = parsers.getParser(mediaType);
if (p == null) {
if (mediaType == MediaType.JSON)
p = JsonParser.DEFAULT;
if (mediaType == MediaType.XML)
p = XmlParser.DEFAULT;
if (mediaType == MediaType.HTML)
p = HtmlParser.DEFAULT;
if (mediaType == MediaType.UON)
p = UonParser.DEFAULT;
if (mediaType == MediaType.URLENCODING)
p = UrlEncodingParser.DEFAULT;
if (mediaType == MediaType.MSGPACK)
p = MsgPackParser.DEFAULT;
}
if (p != null) {
try {
try (Closeable in = p.isReaderParser() ? new InputStreamReader(is, UTF8) : is) {
return p.parse(in, c);
}
} catch (ParseException e) {
throw new ServletException("Could not parse resource '"+name+" as media type '"+mediaType+"'.", e);
}
}
throw new ServletException("Unknown media type '"+mediaType+"'");
} catch (Exception e) {
throw new ServletException("Could not parse resource with name '"+name+"'", e);
}
}
/**
* Returns the path for this resource as defined by the {@link RestResource#path() @RestResource(path)} annotation or
* {@link RestContextBuilder#path(String)} method concatenated with those on all parent classes.
*
*
* If path is not specified, returns "/" .
*
Path always starts with "/" .
*
*
See Also:
*
* - {@link #REST_path}
*
*
* @return The servlet path.
*/
public String getPath() {
return fullPath;
}
/**
* The widgets used for resolving "$W{...}" variables.
*
* See Also:
*
* - {@link #REST_widgets}
*
*
* @return The var resolver widgets as a map with keys being the name returned by {@link Widget#getName()}.
*/
public Map getWidgets() {
return widgets;
}
/**
* Returns the logger to use for this resource.
*
* See Also:
*
* - {@link #REST_logger}
*
*
* @return
* The logger to use for this resource.
*
Never null .
*/
public RestLogger getLogger() {
return logger;
}
/**
* Returns the resource bundle used by this resource.
*
* See Also:
*
* - {@link #REST_messages}
*
*
* @return
* The resource bundle for this resource.
*
Never null .
*/
public MessageBundle getMessages() {
return msgs;
}
/**
* Returns the REST information provider used by this resource.
*
* See Also:
*
* - {@link RestContext#REST_infoProvider}
*
*
* @return
* The information provider for this resource.
*
Never null .
*/
public RestInfoProvider getInfoProvider() {
return infoProvider;
}
/**
* Returns the REST call handler used by this resource.
*
* See Also:
*
* - {@link RestContext#REST_callHandler}
*
*
* @return
* The call handler for this resource.
*
Never null .
*/
public RestCallHandler getCallHandler() {
return callHandler;
}
/**
* Returns a map of HTTP method names to call routers.
*
* @return A map with HTTP method names upper-cased as the keys, and call routers as the values.
*/
protected Map getCallRouters() {
return callRouters;
}
/**
* Returns the resource object.
*
*
* This is the instance of the class annotated with the {@link RestResource @RestResource} annotation, usually
* an instance of {@link RestServlet}.
*
* @return
* The resource object.
*
Never null .
*/
public Object getResource() {
return resource;
}
/**
* Returns the resource object as a {@link RestServlet}.
*
* @return
* The resource object cast to {@link RestServlet}, or null if the resource doesn't subclass from
* {@link RestServlet}.
*/
public RestServlet getRestServlet() {
return resource instanceof RestServlet ? (RestServlet)resource : null;
}
/**
* Throws a {@link RestException} if an exception occurred in the constructor of this object.
*
* @throws RestException The initialization exception wrapped in a {@link RestException}.
*/
protected void checkForInitException() throws RestException {
if (initException != null)
throw initException;
}
/**
* Returns the parent resource context (if this resource was initialized from a parent).
*
*
* From this object, you can get access to the parent resource class itself using {@link #getResource()} or
* {@link #getRestServlet()}
*
* @return The parent resource context, or null if there is no parent context.
*/
public RestContext getParentContext() {
return parentContext;
}
/**
* Returns the {@link BeanContext} object used for parsing path variables and header values.
*
* @return The bean context used for parsing path variables and header values.
*/
public BeanContext getBeanContext() {
return beanContext;
}
/**
* Returns the class-level properties associated with this servlet.
*
*
* Properties at the class level are defined via the following:
*
* - {@link RestResource#properties()}
*
- {@link RestContextBuilder#set(String, Object)}
*
- {@link RestContextBuilder#set(Map)}
*
*
* Notes:
*
* -
* The returned {@code Map} is mutable.
*
Therefore, subclasses are free to override or set additional initialization parameters in their {@code init()} method.
*
*
* @return The resource properties as a {@link RestContextProperties}.
*/
public RestContextProperties getProperties() {
return properties;
}
/**
* Returns the serializers registered with this resource.
*
* See Also:
*
* - {@link RestContext#REST_serializers}
*
*
* @return The serializers registered with this resource.
*/
public SerializerGroup getSerializers() {
return serializers;
}
/**
* Returns the parsers registered with this resource.
*
*
* Parsers at the class level are defined via the following:
*
* - {@link RestContext#REST_parsers}
*
*
* @return The parsers registered with this resource.
*/
public ParserGroup getParsers() {
return parsers;
}
/**
* Returns the servlet init parameter returned by {@link ServletConfig#getInitParameter(String)}.
*
* @param name The init parameter name.
* @return The servlet init parameter, or null if not found.
*/
public String getServletInitParameter(String name) {
return builder.getInitParameter(name);
}
/**
* Returns the child resources associated with this servlet.
*
* @return
* An unmodifiable map of child resources.
* Keys are the {@link RestResource#path() @RestResource(path)} annotation defined on the child resource.
*/
public Map getChildResources() {
return Collections.unmodifiableMap(childResources);
}
/**
* Returns the number of times this exception was thrown based on a hash of its stacktrace.
*
* See Also:
*
* - {@link RestContext#REST_useStackTraceHashes}
*
*
* @param e The exception to check.
* @return
* The number of times this exception was thrown, or 0
if {@link #REST_useStackTraceHashes}
* setting is not enabled.
*/
public int getStackTraceOccurrence(Throwable e) {
if (! useStackTraceHashes)
return 0;
int h = e.hashCode();
stackTraceHashes.putIfAbsent(h, new AtomicInteger());
return stackTraceHashes.get(h).incrementAndGet();
}
/**
* Returns whether it's safe to render stack traces in HTTP responses.
*
* See Also:
*
* - {@link RestContext#REST_useStackTraceHashes}
*
*
* @return true if setting is enabled.
*/
public boolean isRenderResponseStackTraces() {
return renderResponseStackTraces;
}
/**
* Returns whether it's safe to pass header values in as GET parameters.
*
* See Also:
*
* - {@link RestContext#REST_allowHeaderParams}
*
*
* @return true if setting is enabled.
*/
public boolean isAllowHeaderParams() {
return allowHeaderParams;
}
/**
* Returns whether it's safe to pass the HTTP body as a "body" GET parameter.
*
* See Also:
*
* - {@link RestContext#REST_allowBodyParam}
*
*
* @return true if setting is enabled.
*/
public boolean isAllowBodyParam() {
return allowBodyParam;
}
/**
* Returns true if debug mode is enabled on this resource.
*
* See Also:
*
* - {@link RestContext#REST_debug}
*
*
* @return true if setting is enabled.
*/
@Override
public boolean isDebug() {
return debug;
}
/**
* Returns the default charset to use on requests and responses when not specified on the request.
*
* See Also:
*
* - {@link RestContext#REST_defaultCharset}
*
*
* @return
* The default charset.
*
Never null .
*/
public String getDefaultCharset() {
return defaultCharset;
}
/**
* Returns the maximum request input size in bytes.
*
* See Also:
*
* - {@link RestContext#REST_maxInput}
*
*
* @return The maximum request input size in bytes.
*/
public long getMaxInput() {
return maxInput;
}
/**
* Returns the name of the client version header name used by this resource.
*
* See Also:
*
* - {@link RestContext#REST_clientVersionHeader}
*
*
* @return
* The name of the client version header used by this resource.
*
Never null .
*/
public String getClientVersionHeader() {
return clientVersionHeader;
}
/**
* Returns true if the specified Method
GET parameter value can be used to override
* the method name in the HTTP header.
*
* See Also:
*
* - {@link RestContext#REST_allowedMethodParams}
*
*
* @param m The method name, upper-cased.
* @return true if this resource allows the specified method to be overridden.
*/
public boolean allowMethodParam(String m) {
return (isNotEmpty(m) && (allowedMethodParams.contains(m) || allowedMethodParams.contains("*")));
}
/**
* Returns the HTTP-part parser associated with this resource.
*
* See Also:
*
* - {@link RestContext#REST_partParser}
*
*
* @return
* The HTTP-part parser associated with this resource.
*
Never null .
*/
public HttpPartParser getPartParser() {
return partParser;
}
/**
* Returns the HTTP-part serializer associated with this resource.
*
* See Also:
*
* - {@link RestContext#REST_partSerializer}
*
*
* @return
* The HTTP-part serializer associated with this resource.
*
Never null .
*/
public HttpPartSerializer getPartSerializer() {
return partSerializer;
}
/**
* Returns the encoders associated with this resource.
*
* See Also:
*
* - {@link RestContext#REST_encoders}
*
*
* @return
* The encoders associated with this resource.
*
Never null .
*/
public EncoderGroup getEncoders() {
return encoders;
}
/**
* Returns the explicit list of supported accept types for this resource.
*
* See Also:
*
* - {@link RestContext#REST_serializers}
*
- {@link RestContext#REST_produces}
*
*
* @return
* The supported Accept
header values for this resource.
*
Never null .
*/
public List getProduces() {
return produces;
}
/**
* Returns the explicit list of supported content types for this resource.
*
* See Also:
*
* - {@link RestContext#REST_parsers}
*
- {@link RestContext#REST_consumes}
*
*
* @return
* The supported Content-Type
header values for this resource.
*
Never null .
*/
public List getConsumes() {
return consumes;
}
/**
* Returns the default request headers for this resource.
*
* See Also:
*
* - {@link RestContext#REST_defaultRequestHeaders}
*
*
* @return
* The default request headers for this resource.
*
Never null .
*/
public Map getDefaultRequestHeaders() {
return defaultRequestHeaders;
}
/**
* Returns the default response headers for this resource.
*
* See Also:
*
* - {@link RestContext#REST_defaultResponseHeaders}
*
*
* @return
* The default response headers for this resource.
*
Never null .
*/
public Map getDefaultResponseHeaders() {
return defaultResponseHeaders;
}
/**
* Returns the converters associated with this resource at the class level.
*
* See Also:
*
* - {@link RestContext#REST_converters}
*
*
* @return
* The converters associated with this resource.
*
Never null .
*/
public RestConverter[] getConverters() {
return converters;
}
/**
* Returns the guards associated with this resource at the class level.
*
* See Also:
*
* - {@link RestContext#REST_guards}
*
*
* @return
* The guards associated with this resource.
*
Never null .
*/
public RestGuard[] getGuards() {
return guards;
}
/**
* Returns the response handlers associated with this resource.
*
* See Also:
*
* - {@link RestContext#REST_responseHandlers}
*
*
* @return
* The response handlers associated with this resource.
*
Never null .
*/
protected ResponseHandler[] getResponseHandlers() {
return responseHandlers;
}
/**
* Returns true if this resource has any child resources associated with it.
*
* See Also:
*
* - {@link RestContext#REST_children}
*
*
* @return true if this resource has any child resources associated with it.
*/
public boolean hasChildResources() {
return ! childResources.isEmpty();
}
/**
* Returns the context of the child resource associated with the specified path.
*
* See Also:
*
* - {@link RestContext#REST_children}
*
*
* @param path The path of the child resource to resolve.
* @return The resolved context, or null if it could not be resolved.
*/
public RestContext getChildResource(String path) {
return childResources.get(path);
}
/**
* Returns the authority path of the resource.
*
* See Also:
*
* - {@link RestContext#REST_uriAuthority}
*
*
* @return
* The authority path of this resource.
*
If not specified, returns the context path of the ascendant resource.
*/
public String getUriAuthority() {
if (uriAuthority != null)
return uriAuthority;
if (parentContext != null)
return parentContext.getUriAuthority();
return null;
}
/**
* Returns the context path of the resource.
*
* See Also:
*
* - {@link RestContext#REST_uriContext}
*
*
* @return
* The context path of this resource.
*
If not specified, returns the context path of the ascendant resource.
*/
public String getUriContext() {
if (uriContext != null)
return uriContext;
if (parentContext != null)
return parentContext.getUriContext();
return null;
}
/**
* Returns the setting on how relative URIs should be interpreted as relative to.
*
* See Also:
*
* - {@link RestContext#REST_uriRelativity}
*
*
* @return
* The URI-resolution relativity setting value.
*
Never null.
*/
public UriRelativity getUriRelativity() {
return uriRelativity;
}
/**
* Returns the setting on how relative URIs should be resolved.
*
* See Also:
*
* - {@link RestContext#REST_uriResolution}
*
*
* @return
* The URI-resolution setting value.
*
Never null.
*/
public UriResolution getUriResolution() {
return uriResolution;
}
/**
* Returns the parameters defined on the specified Java method.
*
* @param method The Java method to check.
* @return The parameters defined on the Java method.
*/
public RestMethodParam[] getRestMethodParams(Method method) {
return callMethods.get(method.getName()).methodParams;
}
/**
* Returns the media type for the specified file name.
*
* See Also:
*
* - {@link RestContext#REST_mimeTypes}
*
*
* @param name The file name.
* @return The MIME-type, or null if it could not be determined.
*/
public String getMediaTypeForName(String name) {
return mimetypesFileTypeMap.getContentType(name);
}
/**
* Returns true if the specified path refers to a static file.
*
*
* Static files are files pulled from the classpath and served up directly to the browser.
*
*
See Also:
*
* - {@link RestContext#REST_staticFiles}
*
*
* @param p The URL path remainder after the servlet match.
* @return true if the specified path refers to a static file.
*/
public boolean isStaticFile(String p) {
return pathStartsWith(p, staticFilesPaths);
}
/**
* Returns the REST Java methods defined in this resource.
*
*
* These are the methods annotated with the {@link RestMethod @RestMethod} annotation.
*
* @return
* An unmodifiable map of Java method names to call method objects.
*/
public Map getCallMethods() {
return callMethods;
}
/**
* Finds the {@link RestMethodParam} instances to handle resolving objects on the calls to the specified Java method.
*
* @param method The Java method being called.
* @param pathPattern The parsed URL path pattern.
* @param isPreOrPost Whether this is a {@link HookEvent#PRE_CALL} or {@link HookEvent#POST_CALL}.
* @return The array of resolvers.
* @throws ServletException If an annotation usage error was detected.
*/
protected RestMethodParam[] findParams(Method method, UrlPathPattern pathPattern, boolean isPreOrPost) throws ServletException {
Type[] pt = method.getGenericParameterTypes();
RestMethodParam[] rp = new RestMethodParam[pt.length];
PropertyStore ps = getPropertyStore();
for (int i = 0; i < pt.length; i++) {
Type t = pt[i];
if (t instanceof Class) {
Class> c = (Class>)t;
rp[i] = paramResolvers.get(c);
if (rp[i] == null)
rp[i] = RestParamDefaults.STANDARD_RESOLVERS.get(c);
}
if (hasAnnotation(Header.class, method, i)) {
rp[i] = new RestParamDefaults.HeaderObject(method, i, ps);
} else if (hasAnnotation(Query.class, method, i)) {
rp[i] = new RestParamDefaults.QueryObject(method, i, ps);
} else if (hasAnnotation(FormData.class, method, i)) {
rp[i] = new RestParamDefaults.FormDataObject(method, i, ps);
} else if (hasAnnotation(Path.class, method, i)) {
rp[i] = new RestParamDefaults.PathObject(method, i, ps);
} else if (hasAnnotation(Body.class, method, i)) {
rp[i] = new RestParamDefaults.BodyObject(method, i, ps);
} else if (hasAnnotation(Request.class, method, i)) {
rp[i] = new RestParamDefaults.RequestObject(method, i, ps);
} else if (hasAnnotation(Response.class, method, i)) {
rp[i] = new RestParamDefaults.ResponseObject(method, i, ps);
} else if (hasAnnotation(ResponseHeader.class, method, i)) {
rp[i] = new RestParamDefaults.ResponseHeaderObject(method, i, ps);
} else if (hasAnnotation(ResponseStatus.class, method, i)) {
rp[i] = new RestParamDefaults.ResponseStatusObject(method, t);
} else if (hasAnnotation(HasFormData.class, method, i)) {
rp[i] = new RestParamDefaults.HasFormDataObject(method, i);
} else if (hasAnnotation(HasQuery.class, method, i)) {
rp[i] = new RestParamDefaults.HasQueryObject(method, i);
} else if (hasAnnotation(org.apache.juneau.rest.annotation.Method.class, method, i)) {
rp[i] = new RestParamDefaults.MethodObject(method, t);
}
if (rp[i] == null && ! isPreOrPost)
throw new RestServletException("Invalid parameter specified for method ''{0}'' at index position {1}", method, i);
}
return rp;
}
/*
* Calls all @RestHook(PRE) methods.
*/
void preCall(RestRequest req, RestResponse res) throws RestException {
for (int i = 0; i < preCallMethods.length; i++)
preOrPost(resource, preCallMethods[i], preCallMethodParams[i], req, res);
}
/*
* Calls all @RestHook(POST) methods.
*/
void postCall(RestRequest req, RestResponse res) throws RestException {
for (int i = 0; i < postCallMethods.length; i++)
preOrPost(resource, postCallMethods[i], postCallMethodParams[i], req, res);
}
private static void preOrPost(Object resource, Method m, RestMethodParam[] mp, RestRequest req, RestResponse res) throws RestException {
if (m != null) {
Object[] args = new Object[mp.length];
for (int i = 0; i < mp.length; i++) {
try {
args[i] = mp[i].resolve(req, res);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new BadRequest(e,
"Invalid data conversion. Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.",
mp[i].getParamType().name(), mp[i].getName(), mp[i].getType(), m.getDeclaringClass().getName(), m.getName()
);
}
}
try {
m.invoke(resource, args);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
}
/*
* Calls all @RestHook(START) methods.
*/
void startCall(HttpServletRequest req, HttpServletResponse res) {
for (int i = 0; i < startCallMethods.length; i++)
startOrFinish(resource, startCallMethods[i], startCallMethodParams[i], req, res);
}
/*
* Calls all @RestHook(FINISH) methods.
*/
void finishCall(HttpServletRequest req, HttpServletResponse res) {
for (int i = 0; i < endCallMethods.length; i++)
startOrFinish(resource, endCallMethods[i], endCallMethodParams[i], req, res);
}
private static void startOrFinish(Object resource, Method m, Class>[] p, HttpServletRequest req, HttpServletResponse res) throws RestException, InternalServerError {
if (m != null) {
Object[] args = new Object[p.length];
for (int i = 0; i < p.length; i++) {
if (p[i] == HttpServletRequest.class)
args[i] = req;
else if (p[i] == HttpServletResponse.class)
args[i] = res;
}
try {
m.invoke(resource, args);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
}
/**
* Calls all @RestHook(POST_INIT) methods in parent-to-child order.
*
* @return This object (for method chaining).
* @throws ServletException
*/
public RestContext postInit() throws ServletException {
for (int i = 0; i < postInitMethods.length; i++)
postInitOrDestroy(resource, postInitMethods[i], postInitMethodParams[i]);
for (RestContext childContext : this.childResources.values())
childContext.postInit();
return this;
}
/**
* Calls all @RestHook(POST_INIT_CHILD_FIRST) methods in child-to-parent order.
*
* @return This object (for method chaining).
* @throws ServletException
*/
public RestContext postInitChildFirst() throws ServletException {
for (RestContext childContext : this.childResources.values())
childContext.postInitChildFirst();
for (int i = 0; i < postInitChildFirstMethods.length; i++)
postInitOrDestroy(resource, postInitChildFirstMethods[i], postInitChildFirstMethodParams[i]);
return this;
}
private void postInitOrDestroy(Object r, Method m, Class>[] p) {
if (m != null) {
Object[] args = new Object[p.length];
for (int i = 0; i < p.length; i++) {
if (p[i] == RestContext.class)
args[i] = this;
else if (p[i] == RestContextBuilder.class)
args[i] = this.builder;
else if (p[i] == ServletConfig.class)
args[i] = this.builder.inner;
}
try {
m.invoke(r, args);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
}
/**
* Calls {@link Servlet#destroy()} on any child resources defined on this resource.
*/
protected void destroy() {
for (int i = 0; i < destroyMethods.length; i++) {
try {
postInitOrDestroy(resource, destroyMethods[i], destroyMethodParams[i]);
} catch (Exception e) {
e.printStackTrace();
}
}
for (RestContext r : childResources.values()) {
r.destroy();
if (r.resource instanceof Servlet)
((Servlet)r.resource).destroy();
}
}
/**
* Unused.
*/
@Override /* BeanContextBuilder */
public BeanSession createSession(BeanSessionArgs args) {
throw new NoSuchMethodError();
}
/**
* Unused.
*/
@Override /* BeanContextBuilder */
public BeanSessionArgs createDefaultSessionArgs() {
throw new NoSuchMethodError();
}
}