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.ArrayUtils.*;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.IOUtils.*;
import static org.apache.juneau.internal.ReflectionUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import javax.activation.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.juneau.*;
import org.apache.juneau.encoders.*;
import org.apache.juneau.http.*;
import org.apache.juneau.ini.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.annotation.Properties;
import org.apache.juneau.rest.vars.*;
import org.apache.juneau.rest.widget.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.svl.*;
import org.apache.juneau.svl.vars.*;
import org.apache.juneau.urlencoding.*;
import org.apache.juneau.utils.*;
/**
* Contains all the configuration on a REST resource and the entry points for handling REST calls.
*
*
* See {@link PropertyStore} for more information about context properties.
*/
public final class RestContext extends Context {
private final Object resource;
final RestConfig config;
private final boolean
allowHeaderParams,
allowBodyParam,
renderResponseStackTraces,
useStackTraceHashes;
private final String
defaultCharset,
paramFormat,
clientVersionHeader,
fullPath,
contextPath;
private final Map widgets;
private final Set allowMethodParams;
private final ObjectMap properties;
private final Class>[]
beanFilters,
pojoSwaps;
private final Map,RestParam> paramResolvers;
private final SerializerGroup serializers;
private final ParserGroup parsers;
private final UrlEncodingSerializer urlEncodingSerializer;
private final UrlEncodingParser urlEncodingParser;
private final EncoderGroup encoders;
private final MediaType[]
supportedContentTypes,
supportedAcceptTypes;
private final Map defaultRequestHeaders;
private final Map defaultResponseHeaders;
private final BeanContext beanContext;
private final RestConverter[] converters;
private final RestGuard[] guards;
private final ResponseHandler[] responseHandlers;
private final MimetypesFileTypeMap mimetypesFileTypeMap;
private final Map staticFilesMap;
private final String[] staticFilesPrefixes;
private final MessageBundle msgs;
private final ConfigFile configFile;
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;
// Lifecycle methods
private final Method[]
postInitMethods,
postInitChildFirstMethods,
preCallMethods,
postCallMethods,
startCallMethods,
endCallMethods,
destroyMethods;
private final RestParam[][]
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 ResourceFinder resourceFinder;
private final ConcurrentHashMap stackTraceHashes = new ConcurrentHashMap();
/**
* Constructor.
*
* @param resource The resource class (a class annotated with {@link RestResource @RestResource}).
* @param servletContext
* The servlet context object.
* Can be null if this isn't a
* @param config The servlet configuration object.
* @throws Exception If any initialization problems were encountered.
*/
@SuppressWarnings("unchecked")
public RestContext(Object resource, ServletContext servletContext, RestConfig config) throws Exception {
super(null);
RestException _initException = null;
try {
this.resource = resource;
this.config = config;
this.resourceFinder = new ResourceFinder(resource.getClass());
this.parentContext = config.parentContext;
Builder b = new Builder(resource, config);
this.allowHeaderParams = b.allowHeaderParams;
this.allowBodyParam = b.allowBodyParam;
this.renderResponseStackTraces = b.renderResponseStackTraces;
this.useStackTraceHashes = b.useStackTraceHashes;
this.allowMethodParams = Collections.unmodifiableSet(b.allowMethodParams);
this.defaultCharset = b.defaultCharset;
this.paramFormat = b.paramFormat;
this.varResolver = b.varResolver;
this.configFile = b.configFile;
this.properties = b.properties;
this.beanFilters = b.beanFilters;
this.pojoSwaps = b.pojoSwaps;
this.paramResolvers = Collections.unmodifiableMap(b.paramResolvers);
this.serializers = b.serializers;
this.parsers = b.parsers;
this.urlEncodingSerializer = b.urlEncodingSerializer;
this.urlEncodingParser = b.urlEncodingParser;
this.encoders = b.encoders;
this.supportedContentTypes = toObjectArray(b.supportedContentTypes, MediaType.class);
this.supportedAcceptTypes = toObjectArray(b.supportedAcceptTypes, MediaType.class);
this.clientVersionHeader = b.clientVersionHeader;
this.defaultRequestHeaders = Collections.unmodifiableMap(b.defaultRequestHeaders);
this.defaultResponseHeaders = Collections.unmodifiableMap(b.defaultResponseHeaders);
this.beanContext = b.beanContext;
this.converters = b.converters.toArray(new RestConverter[b.converters.size()]);
this.guards = b.guards.toArray(new RestGuard[b.guards.size()]);
this.responseHandlers = toObjectArray(b.responseHandlers, ResponseHandler.class);
this.mimetypesFileTypeMap = b.mimetypesFileTypeMap;
this.staticFilesMap = Collections.unmodifiableMap(b.staticFilesMap);
this.staticFilesPrefixes = b.staticFilesPrefixes;
this.msgs = b.messageBundle;
this.childResources = Collections.synchronizedMap(new LinkedHashMap()); // Not unmodifiable on purpose so that children can be replaced.
this.logger = b.logger;
this.fullPath = b.fullPath;
this.contextPath = nullIfEmpty(b.contextPath);
this.widgets = Collections.unmodifiableMap(b.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 : resource.getClass().getMethods()) {
if (method.isAnnotationPresent(RestMethod.class)) {
RestMethod a = method.getAnnotation(RestMethod.class);
methodsFound.add(method.getName() + "," + a.name() + "," + a.path());
try {
if (! Modifier.isPublic(method.getModifiers()))
throw new RestServletException("@RestMethod method {0}.{1} must be defined as public.", this.getClass().getName(), method.getName());
CallMethod sm = new CallMethod(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 Map remoteableMethods = interfaceClass.getRemoteableMethods();
if (remoteableMethods.isEmpty())
throw new RestException(SC_INTERNAL_SERVER_ERROR, "Method {0} returns an interface {1} that doesn't define any remoteable methods.", getMethodSignature(method), interfaceClass.getReadableName());
sm = new CallMethod(resource, method, this) {
@Override
int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
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(getMethodInfo(remoteableMethods.values()));
return SC_OK;
} else if ("POST".equals(req.getMethod())) {
if (pathInfo.indexOf('/') != -1)
pathInfo = pathInfo.substring(pathInfo.lastIndexOf('/')+1);
pathInfo = urlDecode(pathInfo);
java.lang.reflect.Method m = remoteableMethods.get(pathInfo);
if (m != null) {
try {
// Parse the args and invoke the method.
Parser p = req.getBody().getParser();
Object input = p.isReaderParser() ? req.getReader() : req.getInputStream();
Object output = m.invoke(o, p.parseArgs(input, m.getGenericParameterTypes()));
res.setOutput(output);
return SC_OK;
} catch (Exception e) {
throw new RestException(SC_INTERNAL_SERVER_ERROR, 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 (RestServletException e) {
throw new RestServletException("Problem occurred trying to serialize methods on class {0}, methods={1}", this.getClass().getName(), JsonSerializer.DEFAULT_LAX.serialize(methodsFound)).initCause(e);
}
}
}
for (Method m : ClassUtils.getAllMethods(resource.getClass(), 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)) {
Visibility.setAccessible(m);
_preCallMethods.put(sig, m);
_preCallMethodParams.add(findParams(m, false, null, true));
}
break;
}
case POST_CALL: {
if (! _postCallMethods.containsKey(sig)) {
Visibility.setAccessible(m);
_postCallMethods.put(sig, m);
_postCallMethodParams.add(findParams(m, false, null, true));
}
break;
}
case START_CALL: {
if (! _startCallMethods.containsKey(sig)) {
Visibility.setAccessible(m);
_startCallMethods.put(sig, m);
_startCallMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, HttpServletRequest.class, HttpServletResponse.class);
}
break;
}
case END_CALL: {
if (! _endCallMethods.containsKey(sig)) {
Visibility.setAccessible(m);
_endCallMethods.put(sig, m);
_endCallMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, HttpServletRequest.class, HttpServletResponse.class);
}
break;
}
case POST_INIT: {
if (! _postInitMethods.containsKey(sig)) {
Visibility.setAccessible(m);
_postInitMethods.put(sig, m);
_postInitMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, RestContext.class);
}
break;
}
case POST_INIT_CHILD_FIRST: {
if (! _postInitChildFirstMethods.containsKey(sig)) {
Visibility.setAccessible(m);
_postInitChildFirstMethods.put(sig, m);
_postInitChildFirstMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, RestContext.class);
}
break;
}
case DESTROY: {
if (! _destroyMethods.containsKey(sig)) {
Visibility.setAccessible(m);
_destroyMethods.put(sig, m);
_destroyMethodParams.add(m.getParameterTypes());
ClassUtils.assertArgsOfType(m, RestContext.class);
}
break;
}
default: // Ignore INIT
}
}
}
this.callMethods = Collections.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 RestParam[_preCallMethodParams.size()][]);
this.postCallMethodParams = _postCallMethodParams.toArray(new RestParam[_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 (CallRouter.Builder crb : routers.values())
_callRouters.put(crb.getHttpMethodName(), crb.build());
this.callRouters = Collections.unmodifiableMap(_callRouters);
// Initialize our child resources.
resourceResolver = resolve(resource, RestResourceResolver.class, b.resourceResolver);
for (Object o : config.childResources) {
String path = null;
Object r = null;
if (o instanceof Pair) {
Pair p = (Pair)o;
path = p.first();
r = p.second();
} else if (o instanceof Class>) {
Class> c = (Class>)o;
// Don't allow specifying yourself as a child. Causes an infinite loop.
if (c == config.resourceClass)
continue;
r = c;
} else {
r = o;
}
RestConfig childConfig = null;
if (o instanceof Class) {
Class> oc = (Class>)o;
childConfig = new RestConfig(config.inner, oc, this);
r = resourceResolver.resolve(oc, childConfig);
} else {
r = o;
childConfig = new RestConfig(config.inner, o.getClass(), this);
}
childConfig.init(r);
if (r instanceof RestServlet)
((RestServlet)r).innerInit(childConfig);
RestContext rc2 = new RestContext(r, servletContext, childConfig);
if (r instanceof RestServlet)
((RestServlet)r).setContext(rc2);
path = childConfig.path;
childResources.put(path, rc2);
}
callHandler = config.callHandler == null ? new RestCallHandler(this) : resolve(resource, RestCallHandler.class, config.callHandler, this);
infoProvider = config.infoProvider == null ? new RestInfoProvider(this) : resolve(resource, RestInfoProvider.class, config.infoProvider, this);
} catch (RestException e) {
_initException = e;
throw e;
} catch (Exception e) {
_initException = new RestException(SC_INTERNAL_SERVER_ERROR, e);
throw e;
} finally {
initException = _initException;
}
}
private static void addToRouter(Map routers, String httpMethodName, CallMethod cm) throws RestServletException {
if (! routers.containsKey(httpMethodName))
routers.put(httpMethodName, new CallRouter.Builder(httpMethodName));
routers.get(httpMethodName).add(cm);
}
private static class Builder {
boolean allowHeaderParams, allowBodyParam, renderResponseStackTraces, useStackTraceHashes;
VarResolver varResolver;
ConfigFile configFile;
ObjectMap properties;
Class>[] beanFilters;
Class>[] pojoSwaps;
Map,RestParam> paramResolvers = new HashMap,RestParam>();
SerializerGroup serializers;
ParserGroup parsers;
UrlEncodingSerializer urlEncodingSerializer;
UrlEncodingParser urlEncodingParser;
EncoderGroup encoders;
String clientVersionHeader = "", defaultCharset, paramFormat;
List supportedContentTypes, supportedAcceptTypes;
Map defaultRequestHeaders = new TreeMap(String.CASE_INSENSITIVE_ORDER);
Map defaultResponseHeaders;
BeanContext beanContext;
List converters = new ArrayList();
List guards = new ArrayList();
List responseHandlers = new ArrayList();
MimetypesFileTypeMap mimetypesFileTypeMap;
Map staticFilesMap;
String[] staticFilesPrefixes;
MessageBundle messageBundle;
Set allowMethodParams = new LinkedHashSet();
RestLogger logger;
String fullPath;
Map widgets;
Object resourceResolver;
String contextPath;
@SuppressWarnings("unchecked")
private Builder(Object resource, RestConfig sc) throws Exception {
PropertyStore ps = sc.createPropertyStore();
LinkedHashMap,RestResource> restResourceAnnotationsChildFirst = findAnnotationsMap(RestResource.class, resource.getClass());
allowHeaderParams = getBoolean(sc.allowHeaderParams, "juneau.allowHeaderParams", true);
allowBodyParam = getBoolean(sc.allowBodyParam, "juneau.allowBodyParam", true);
renderResponseStackTraces = getBoolean(sc.renderResponseStackTraces, "juneau.renderResponseStackTraces", false);
useStackTraceHashes = getBoolean(sc.useStackTraceHashes, "juneau.useStackTraceHashes", true);
defaultCharset = getString(sc.defaultCharset, "juneau.defaultCharset", "utf-8");
paramFormat = getString(sc.paramFormat, "juneau.paramFormat", "UON");
resourceResolver = sc.resourceResolver;
String amp = getString(sc.allowMethodParam, "juneau.allowMethodParam", "HEAD,OPTIONS");
if ("true".equals(amp))
amp = "*";// For backwards compatibility when this was a boolean field.
else
amp = amp.toUpperCase();
allowMethodParams.addAll(Arrays.asList(StringUtils.split(amp)));
varResolver = sc.varResolverBuilder
.vars(FileVar.class, LocalizationVar.class, RequestVar.class, SerializedRequestAttrVar.class, ServletInitParamVar.class, UrlVar.class, UrlEncodeVar.class, WidgetVar.class)
.build()
;
configFile = sc.configFile.getResolving(this.varResolver);
properties = sc.properties;
Collections.reverse(sc.beanFilters);
Collections.reverse(sc.pojoSwaps);
beanFilters = toObjectArray(sc.beanFilters, Class.class);
pojoSwaps = toObjectArray(sc.pojoSwaps, Class.class);
for (Class> c : sc.paramResolvers) {
RestParam rp = newInstanceFromOuter(resource, RestParam.class, c);
paramResolvers.put(rp.forClass(), rp);
}
clientVersionHeader = sc.clientVersionHeader;
// Find resource resource bundle location.
for (Map.Entry,RestResource> e : restResourceAnnotationsChildFirst.entrySet()) {
Class> c = e.getKey();
RestResource r = e.getValue();
if (! r.messages().isEmpty()) {
if (messageBundle == null)
messageBundle = new MessageBundle(c, r.messages());
else
messageBundle.addSearchPath(c, r.messages());
}
}
if (messageBundle == null)
messageBundle = new MessageBundle(resource.getClass(), "");
ps.addBeanFilters(beanFilters).addPojoSwaps(pojoSwaps).setProperties(properties);
serializers = sc.serializers.beanFilters(beanFilters).pojoSwaps(pojoSwaps).properties(properties).listener(sc.serializerListener).build();
parsers = sc.parsers.beanFilters(beanFilters).pojoSwaps(pojoSwaps).properties(properties).listener(sc.parserListener).build();
urlEncodingSerializer = new UrlEncodingSerializer(ps);
urlEncodingParser = new UrlEncodingParser(ps);
encoders = sc.encoders.build();
supportedContentTypes = sc.supportedContentTypes != null ? sc.supportedContentTypes : serializers.getSupportedMediaTypes();
supportedAcceptTypes = sc.supportedAcceptTypes != null ? sc.supportedAcceptTypes : parsers.getSupportedMediaTypes();
defaultRequestHeaders.putAll(sc.defaultRequestHeaders);
defaultResponseHeaders = Collections.unmodifiableMap(new LinkedHashMap(sc.defaultResponseHeaders));
beanContext = ps.getBeanContext();
contextPath = sc.contextPath;
for (Object o : sc.converters)
converters.add(resolve(resource, RestConverter.class, o));
for (Object o : sc.guards)
guards.add(resolve(resource, RestGuard.class, o));
for (Object o : sc.responseHandlers)
responseHandlers.add(resolve(resource, ResponseHandler.class, o));
mimetypesFileTypeMap = sc.mimeTypes;
VarResolver vr = sc.getVarResolverBuilder().build();
staticFilesMap = new LinkedHashMap();
if (sc.staticFiles != null) {
for (Object o : sc.staticFiles) {
if (o instanceof Pair) {
Pair,String> p = (Pair,String>)o;
// TODO - Currently doesn't take parent class location into account.
staticFilesMap.putAll(JsonParser.DEFAULT.parse(vr.resolve(p.second()), LinkedHashMap.class));
} else {
throw new RuntimeException("TODO");
}
}
}
staticFilesPrefixes = staticFilesMap.keySet().toArray(new String[0]);
logger = sc.logger == null ? new RestLogger.NoOp() : resolve(resource, RestLogger.class, sc.logger);
fullPath = (sc.parentContext == null ? "" : (sc.parentContext.fullPath + '/')) + sc.path;
HtmlDocBuilder hdb = new HtmlDocBuilder(sc.properties);
this.widgets = new LinkedHashMap();
for (Class extends Widget> wc : sc.widgets) {
Widget w = resolve(resource, Widget.class, wc);
String n = w.getName();
this.widgets.put(n, w);
hdb.script("INHERIT", "$W{"+n+".script}");
hdb.style("INHERIT", "$W{"+n+".style}");
}
}
}
private static boolean getBoolean(Object o, String systemProperty, boolean def) {
if (o == null)
o = SystemUtils.getFirstBoolean(def, systemProperty);
return "true".equalsIgnoreCase(o.toString());
}
private static String getString(Object o, String systemProperty, String def) {
if (o == null)
o = SystemUtils.getFirstString(def, systemProperty);
return o.toString();
}
/**
* Returns the resource resolver associated with this context.
*
*
* The resource resolver is used for instantiating child resource classes.
*
*
* Unless overridden via the {@link RestResource#resourceResolver()} annotation or the {@link RestConfig#setResourceResolver(Class)}
* method, this value is always inherited from parent to child.
* This allows a single resource resolver to be passed in to the top-level servlet to handle instantiation of all
* child resources.
*
* @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="v2" ,value="$R{$R{foo}}" ) // Nested request variable. value="baz"
* }
* )
* public class MyRestResource extends RestServletDefault {
*
*
*
* A typical usage pattern involves using variables inside the {@link 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 {
*
*
*
* The following is the default list of supported variables:
*
* $C{key[,defaultValue]}
- Config file entry. See {@link ConfigFileVar}.
* $E{envVar[,defaultValue]}
- Environment variable. See {@link EnvVariablesVar}.
* $F{path[,defaultValue]}
- File resource. See {@link FileVar}.
* $I{name[,defaultValue]}
- Servlet init parameter. See {@link ServletInitParamVar}.
* $L{key[,args...]}
- Localized message. See {@link LocalizationVar}.
* $R{key[,args...]}
- Request variable. See {@link RequestVar}.
* $S{systemProperty[,defaultValue]}
- System property. See {@link SystemPropertiesVar}.
* $SA{contentType,key[,defaultValue]}
- Serialized request attribute. See {@link SerializedRequestAttrVar}.
* $U{uri}
- URI resolver. See {@link UrlVar}.
* $UE{uriPart}
- URL-Encoder. See {@link UrlEncodeVar}.
* $W{widgetName}
- HTML widget variable. See {@link WidgetVar}.
*
*
*
* The following syntax variables are also provided:
*
* $IF{booleanArg,thenValue[,elseValue]}
- If/else variable. See {@link IfVar}.
* $SW{stringArg(,pattern,thenValue)+[,elseValue]}
- Switch variable. See {@link SwitchVar}.
*
*
*
* The list of variables can be extended using the {@link RestConfig#addVars(Class...)} method.
* For example, this is used to add support for the Args and Manifest-File variables in the microservice
* Resource
class.
*
* @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() @RestResource.config()} annotation.
*
- {@link RestConfig#setConfigFile(ConfigFile)} method.
*
*
* @return The resolving config file associated with this servlet. Never null .
*/
public ConfigFile getConfigFile() {
return configFile;
}
/**
* Resolve a static resource file.
*
*
* The location of static resources are defined via one of the following:
*
* - {@link RestResource#staticFiles() @RestResource.staticFiles()} annotation.
*
- {@link RestConfig#addStaticFiles(Class, String)} method.
*
*
* @param pathInfo The unencoded path info.
* @return The resource, or null if the resource could not be resolved.
* @throws IOException
*/
public StreamResource resolveStaticFile(String pathInfo) throws IOException {
if (! staticFilesCache.containsKey(pathInfo)) {
String p = urlDecode(trimSlashes(pathInfo));
if (p.indexOf("..") != -1)
throw new RestException(SC_NOT_FOUND, "Invalid path");
for (Map.Entry e : staticFilesMap.entrySet()) {
String key = trimSlashes(e.getKey());
if (p.startsWith(key)) {
String remainder = (p.equals(key) ? "" : p.substring(key.length()));
if (remainder.isEmpty() || remainder.startsWith("/")) {
String p2 = trimSlashes(e.getValue()) + remainder;
InputStream is = getResource(p2, null);
if (is != null) {
try {
int i = p2.lastIndexOf('/');
String name = (i == -1 ? p2 : p2.substring(i+1));
String mediaType = mimetypesFileTypeMap.getContentType(name);
ObjectMap headers = new ObjectMap().append("Cache-Control", "max-age=86400, public");
staticFilesCache.put(pathInfo, new StreamResource(MediaType.forString(mediaType), headers, is));
return staticFilesCache.get(pathInfo);
} finally {
is.close();
}
}
}
}
}
}
return staticFilesCache.get(pathInfo);
}
/**
* 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"
*
*
* @param name The resource name.
* @param locale Optional locale.
* @return An input stream of the resource, or null if the resource could not be found.
* @throws IOException
*/
protected InputStream getResource(String name, Locale locale) throws IOException {
return resourceFinder.getResourceAsStream(name, locale);
}
/**
* Reads the input stream from {@link #getResource(String, Locale)} into a String.
*
* @param name The resource name.
* @param locale Optional locale.
* @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 getResourceAsString(String name, Locale locale) throws IOException {
return resourceFinder.getResourceAsString(name, locale);
}
/**
* Reads the input stream from {@link #getResource(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.
*
* @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.
* @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 getResource(Class c, MediaType mediaType, String name, Locale locale) throws IOException, ServletException {
InputStream is = getResource(name, locale);
if (is == null)
return null;
try {
Parser p = parsers.getParser(mediaType);
if (p != null) {
try {
if (p.isReaderParser())
return p.parse(new InputStreamReader(is, UTF8), c);
return p.parse(is, c);
} catch (ParseException e) {
throw new ServletException("Could not parse resource '' as media type '"+mediaType+"'.");
}
}
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()} annotation or
* {@link RestConfig#setPath(String)} method concatenated with those on all parent classes.
*
*
* If path is not specified, returns "/" .
*
*
* Path always starts with "/" .
*
* @return The servlet path.
*/
public String getPath() {
return fullPath;
}
/**
* The widgets used for resolving "$W{...}" variables.
*
*
* Defined by the {@link HtmlDoc#widgets()} annotation or {@link RestConfig#addWidget(Class)} method.
*
* @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.
*
*
* The logger for a resource is defined via one of the following:
*
* - {@link RestResource#logger() @RestResource.logger()} annotation.
*
- {@link RestConfig#setLogger(Class)}/{@link RestConfig#setLogger(RestLogger)} methods.
*
*
* @return The logger to use for this resource. Never null .
*/
public RestLogger getLogger() {
return logger;
}
/**
* Returns the resource bundle used by this resource.
*
*
* The resource bundle is defined via one of the following:
*
* - {@link RestResource#messages() @RestResource.messages()} annotation.
*
*
* @return The resource bundle for this resource. Never null .
*/
public MessageBundle getMessages() {
return msgs;
}
/**
* Returns the REST information provider used by this resource.
*
*
* The information provider is defined via one of the following:
*
* - {@link RestResource#infoProvider() @RestResource.infoProvider()} annotation.
*
- {@link RestConfig#setInfoProvider(Class)}/{@link RestConfig#setInfoProvider(RestInfoProvider)} methods.
*
*
* @return The information provider for this resource. Never null .
*/
public RestInfoProvider getInfoProvider() {
return infoProvider;
}
/**
* Returns the REST call handler used by this resource.
*
*
* The call handler is defined via one of the following:
*
* - {@link RestResource#callHandler() @RestResource.callHandler()} annotation.
*
- {@link RestConfig#setCallHandler(Class)}/{@link RestConfig#setCallHandler(RestCallHandler)} methods.
*
*
* @return The call handler for this resource. Never null .
*/
protected 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 one of the following:
*
* - {@link RestResource#properties() @RestResource.properties()} annotation.
*
- {@link RestConfig#setProperty(String, Object)}/{@link RestConfig#setProperties(Map)} methods.
*
*
* 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 an {@link ObjectMap}.
*/
public ObjectMap getProperties() {
return properties;
}
/**
* Returns the serializers registered with this resource.
*
*
* Serializers at the class level are defined via one of the following:
*
* - {@link RestResource#serializers() @RestResource.serializers()} annotation.
*
- {@link RestConfig#addSerializers(Class...)}/{@link RestConfig#addSerializers(Serializer...)} methods.
*
*
* @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 one of the following:
*
* - {@link RestResource#parsers() @RestResource.parsers()} annotation.
*
- {@link RestConfig#addParsers(Class...)}/{@link RestConfig#addParsers(Parser...)} methods.
*
*
* @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 config.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.
*
* @param e The exception to check.
* @return
* The number of times this exception was thrown, or 0
if stackTraceHashes
* setting is not enabled.
*/
protected int getStackTraceOccurrence(Throwable e) {
if (! useStackTraceHashes)
return 0;
int h = e.hashCode();
stackTraceHashes.putIfAbsent(h, new AtomicInteger());
return stackTraceHashes.get(h).incrementAndGet();
}
/**
* Returns the value of the {@link RestResource#renderResponseStackTraces()} setting.
*
* @return The value of the {@link RestResource#renderResponseStackTraces()} setting.
*/
protected boolean isRenderResponseStackTraces() {
return renderResponseStackTraces;
}
/**
* Returns the value of the {@link RestResource#allowHeaderParams()} setting.
*
* @return The value of the {@link RestResource#allowHeaderParams()} setting.
*/
protected boolean isAllowHeaderParams() {
return allowHeaderParams;
}
/**
* Returns the value of the {@link RestResource#allowBodyParam()} setting.
*
* @return The value of the {@link RestResource#allowBodyParam()} setting.
*/
protected boolean isAllowBodyParam() {
return allowBodyParam;
}
/**
* Returns the value of the {@link RestResource#defaultCharset()} setting.
*
* @return The value of the {@link RestResource#defaultCharset()} setting.
*/
protected String getDefaultCharset() {
return defaultCharset;
}
/**
* Returns the value of the {@link RestResource#paramFormat()} setting.
*
* @return The value of the {@link RestResource#paramFormat()} setting.
*/
protected String getParamFormat() {
return paramFormat;
}
/**
* Returns the name of the client version header name used by this resource.
*
*
* The client version header is the name of the HTTP header on requests that identify a client version.
*
*
* The client version header is defined via one of the following:
*
* - {@link RestResource#clientVersionHeader() @RestResource.clientVersion()} annotation.
*
*
* @return The name of the client version header used by this resource. Never null .
*/
protected 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.
*
* @param m The method name, upper-cased.
* @return true if this resource allows the specified method to be overridden.
*/
protected boolean allowMethodParam(String m) {
return (! isEmpty(m) && (allowMethodParams.contains(m) || allowMethodParams.contains("*")));
}
/**
* Returns the bean filters associated with this resource.
*
*
* Bean filters at the class level are defined via one of the following:
*
* - {@link RestResource#beanFilters() @RestResource.beanFilters()} annotation.
*
- {@link RestConfig#addBeanFilters(Class...)} method.
*
*
* @return The bean filters associated with this resource. Never null .
*/
protected Class>[] getBeanFilters() {
return beanFilters;
}
/**
* Returns the POJO swaps associated with this resource.
*
*
* POJO swaps at the class level are defined via one of the following:
*
* - {@link RestResource#pojoSwaps() @RestResource.pojoSwaps()} annotation.
*
- {@link RestConfig#addPojoSwaps(Class...)} method.
*
*
* @return The POJO swaps associated with this resource. Never null .
*/
protected Class>[] getPojoSwaps() {
return pojoSwaps;
}
/**
* Finds the {@link RestParam} instances to handle resolving objects on the calls to the specified Java method.
*
* @param method The Java method being called.
* @param methodPlainParams Whether plain-params setting is specified.
* @param pathPattern The parsed URL path pattern.
* @param isPreOrPost Whether this is a @RestMethodPre or @RestMethodPost .
* @return The array of resolvers.
* @throws ServletException If an annotation usage error was detected.
*/
protected RestParam[] findParams(Method method, boolean methodPlainParams, UrlPathPattern pathPattern, boolean isPreOrPost) throws ServletException {
Type[] pt = method.getGenericParameterTypes();
Annotation[][] pa = method.getParameterAnnotations();
RestParam[] rp = new RestParam[pt.length];
int attrIndex = 0;
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 (rp[i] == null) {
for (Annotation a : pa[i]) {
if (a instanceof Header)
rp[i] = new RestParamDefaults.HeaderObject((Header)a, t);
else if (a instanceof FormData)
rp[i] = new RestParamDefaults.FormDataObject(method, (FormData)a, t, methodPlainParams);
else if (a instanceof Query)
rp[i] = new RestParamDefaults.QueryObject(method, (Query)a, t, methodPlainParams);
else if (a instanceof HasFormData)
rp[i] = new RestParamDefaults.HasFormDataObject(method, (HasFormData)a, t);
else if (a instanceof HasQuery)
rp[i] = new RestParamDefaults.HasQueryObject(method, (HasQuery)a, t);
else if (a instanceof Body)
rp[i] = new RestParamDefaults.BodyObject(t);
else if (a instanceof org.apache.juneau.rest.annotation.Method)
rp[i] = new RestParamDefaults.MethodObject(method, t);
else if (a instanceof PathRemainder)
rp[i] = new RestParamDefaults.PathRemainderObject(method, t);
else if (a instanceof Properties)
rp[i] = new RestParamDefaults.PropsObject(method, t);
else if (a instanceof Messages)
rp[i] = new RestParamDefaults.MessageBundleObject();
}
}
if (rp[i] == null) {
if (isPreOrPost)
throw new RestServletException("Invalid parameter specified for method ''{0}'' at index position {1}", method, i);
Path p = null;
for (Annotation a : pa[i])
if (a instanceof Path)
p = (Path)a;
String name = (p == null ? "" : firstNonEmpty(p.name(), p.value()));
if (isEmpty(name)) {
int idx = attrIndex++;
String[] vars = pathPattern.getVars();
if (vars.length <= idx)
throw new RestServletException("Number of attribute parameters in method ''{0}'' exceeds the number of URL pattern variables.", method);
// Check for {#} variables.
String idxs = String.valueOf(idx);
for (int j = 0; j < vars.length; j++)
if (isNumeric(vars[j]) && vars[j].equals(idxs))
name = vars[j];
if (isEmpty(name))
name = pathPattern.getVars()[idx];
}
rp[i] = new RestParamDefaults.PathParameterObject(name, t);
}
}
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, RestParam[] 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 RestException(SC_BAD_REQUEST,
"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()
).initCause(e);
}
}
try {
m.invoke(resource, args);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new RestException(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()).initCause(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) {
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 RestException(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()).initCause(e);
}
}
}
/*
* Calls all @RestHook(POST_INIT) methods.
*/
void 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();
}
/*
* Calls all @RestHook(POST_INIT_CHILD_FIRST) methods.
*/
void 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]);
}
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] == RestConfig.class)
args[i] = this.config;
else if (p[i] == ServletConfig.class)
args[i] = this.config.inner;
}
try {
m.invoke(r, args);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new RestException(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()).initCause(e);
}
}
}
/**
* Returns the URL-encoding parser associated with this resource.
*
* @return The URL-encoding parser associated with this resource. Never null .
*/
protected UrlEncodingParser getUrlEncodingParser() {
return urlEncodingParser;
}
/**
* Returns the URL-encoding serializer associated with this resource.
*
* @return The URL-encoding serializer associated with this resource. Never null .
*/
protected UrlEncodingSerializer getUrlEncodingSerializer() {
return urlEncodingSerializer;
}
/**
* Returns the encoders associated with this resource.
*
*
* Encoders are used to provide various types of encoding such as gzip
encoding.
*
*
* Encoders at the class level are defined via one of the following:
*
* - {@link RestResource#encoders() @RestResource.encoders()} annotation.
*
- {@link RestConfig#addEncoders(Class...)}/{@link RestConfig#addEncoders(org.apache.juneau.encoders.Encoder...)}
* methods.
*
*
* @return The encoders associated with this resource. Never null .
*/
protected EncoderGroup getEncoders() {
return encoders;
}
/**
* Returns the explicit list of supported accept types for this resource.
*
*
* By default, this is simply the list of accept types supported by the registered parsers, but
* can be overridden via the {@link RestConfig#setSupportedAcceptTypes(MediaType...)}/{@link RestConfig#setSupportedAcceptTypes(String...)}
* methods.
*
* @return The supported Accept
header values for this resource. Never null .
*/
protected MediaType[] getSupportedAcceptTypes() {
return supportedAcceptTypes;
}
/**
* Returns the explicit list of supported content types for this resource.
*
*
* By default, this is simply the list of content types supported by the registered serializers, but can be
* overridden via the {@link RestConfig#setSupportedContentTypes(MediaType...)}/{@link RestConfig#setSupportedContentTypes(String...)}
* methods.
*
* @return The supported Content-Type
header values for this resource. Never null .
*/
protected MediaType[] getSupportedContentTypes() {
return supportedContentTypes;
}
/**
* Returns the default request headers for this resource.
*
*
* These are headers automatically added to requests if not present.
*
*
* Default request headers are defined via one of the following:
*
* - {@link RestResource#defaultRequestHeaders() @RestResource.defaultRequestHeaders()} annotation.
*
- {@link RestConfig#addDefaultRequestHeader(String, Object)}/{@link RestConfig#addDefaultRequestHeaders(String...)} methods.
*
*
* @return The default request headers for this resource. Never null .
*/
protected Map getDefaultRequestHeaders() {
return defaultRequestHeaders;
}
/**
* Returns the default response headers for this resource.
*
*
* These are headers automatically added to responses if not otherwise specified during the request.
*
*
* Default response headers are defined via one of the following:
*
* - {@link RestResource#defaultResponseHeaders() @RestResource.defaultResponseHeaders()} annotation.
*
- {@link RestConfig#addDefaultResponseHeader(String, Object)}/{@link RestConfig#addDefaultResponseHeaders(String...)}
* methods.
*
*
* @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.
*
*
* Converters are used to 'convert' POJOs from one form to another before being passed of to the response handlers.
*
*
* Converters at the class level are defined via one of the following:
*
* - {@link RestResource#converters() @RestResource.converters()} annotation.
*
- {@link RestConfig#addConverters(Class...)}/{@link RestConfig#addConverters(RestConverter...)} methods.
*
*
* @return The converters associated with this resource. Never null .
*/
protected RestConverter[] getConverters() {
return converters;
}
/**
* Returns the guards associated with this resource at the class level.
*
*
* Guards are used to restrict access to resources.
*
*
* Guards at the class level are defined via one of the following:
*
* - {@link RestResource#guards() @RestResource.guards()} annotation.
*
- {@link RestConfig#addGuards(Class...)}/{@link RestConfig#addGuards(RestGuard...)} methods.
*
*
* @return The guards associated with this resource. Never null .
*/
protected RestGuard[] getGuards() {
return guards;
}
/**
* Returns the response handlers associated with this resource.
*
*
* Response handlers are used to convert POJOs returned by REST Java methods into actual HTTP responses.
*
*
* Response handlers are defined via one of the following:
*
* - {@link RestResource#responseHandlers() @RestResource.responseHandlers()} annotation.
*
- {@link RestConfig#addResponseHandlers(Class...)}/{@link RestConfig#addResponseHandlers(ResponseHandler...)}
* methods.
*
*
* @return The response handlers associated with this resource. Never null .
*/
protected ResponseHandler[] getResponseHandlers() {
return responseHandlers;
}
/**
* Returns the media type for the specified file name.
*
*
* The list of MIME-type mappings can be augmented through the {@link RestConfig#addMimeTypes(String...)} method.
* See that method for a description of predefined MIME-type mappings.
*
* @param name The file name.
* @return The MIME-type, or null if it could not be determined.
*/
protected 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.
*
*
* Static files are defined via one of the following:
*
* - {@link RestResource#staticFiles() @RestResource.staticFiles()} annotation.
*
- {@link RestConfig#addStaticFiles(Class, String)} method.
*
*
* @param p The URL path remainder after the servlet match.
* @return true if the specified path refers to a static file.
*/
protected boolean isStaticFile(String p) {
return pathStartsWith(p, staticFilesPrefixes);
}
/**
* Returns the REST Java methods defined in this resource.
*
*
* These are the methods annotated with the {@link RestMethod @RestMethod} annotation.
*
* @return A map of Java method names to call method objects.
*/
protected Map getCallMethods() {
return callMethods;
}
/**
* 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();
}
}
/**
* Returns true if this resource has any child resources associated with it.
*
* @return true if this resource has any child resources associated with it.
*/
protected boolean hasChildResources() {
return ! childResources.isEmpty();
}
/**
* Returns the context of the child resource associated with the specified path.
*
* @param path The path of the child resource to resolve.
* @return The resolved context, or null if it could not be resolved.
*/
protected RestContext getChildResource(String path) {
return childResources.get(path);
}
/**
* Returns the context path of the resource if it's specified via the {@link RestResource#contextPath()} setting
* on this or a parent resource.
*
* @return The {@link RestResource#contextPath()} setting value, or null if it's not specified.
*/
protected String getContextPath() {
if (contextPath != null)
return contextPath;
if (parentContext != null)
return parentContext.getContextPath();
return null;
}
//----------------------------------------------------------------------------------------------------
// Utility methods
//----------------------------------------------------------------------------------------------------
/**
* Takes in an object of type T or a Class and either casts or constructs a T.
*/
private static T resolve(Object outer, Class c, Object o, Object...cArgs) throws RestServletException {
try {
return ClassUtils.newInstanceFromOuter(outer, c, o, cArgs);
} catch (Exception e) {
throw new RestServletException("Exception occurred while constructing class ''{0}''", c).initCause(e);
}
}
}