Added:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
==============================================================================
---
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
(added)
+++
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
Fri Sep 8 23:25:34 2017
@@ -0,0 +1,2053 @@
+//
***************************************************************************************************************************
+// * 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.html.*;
+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.
+ *
+ * <p>
+ * See {@link PropertyStore} for more information about context properties.
+ */
+public final class RestContext extends Context {
+
+ /**
+ * <b>Configuration property:</b> Enable header URL parameters.
+ *
+ * <ul>
+ * <li><b>Name:</b> <js>"RestServlet.allowHeaderParams"</js>
+ * <li><b>Data type:</b> <code>Boolean</code>
+ * <li><b>Default:</b> <jk>true</jk>
+ * </ul>
+ *
+ * <p>
+ * When enabled, headers such as <js>"Accept"</js> and
<js>"Content-Type"</js> to be passed in as URL query
+ * parameters.
+ * For example: <js>"?Accept=text/json&Content-Type=text/json"</js>
+ *
+ * <p>
+ * Parameter names are case-insensitive.
+ *
+ * <p>
+ * Useful for debugging REST interface using only a browser.
+ *
+ * <p>
+ * Applicable to servlet class only.
+ */
+ public static final String REST_allowHeaderParams =
"RestServlet.allowHeaderParams";
+
+ /**
+ * <b>Configuration property:</b> Enable <js>"method"</js> URL
parameter for specific HTTP methods.
+ *
+ * <ul>
+ * <li><b>Name:</b> <js>"RestServlet.allowMethodParam"</js>
+ * <li><b>Data type:</b> <code>String</code>
+ * <li><b>Default:</b> <js>""</js>
+ * </ul>
+ *
+ * <p>
+ * When specified, the HTTP method can be overridden by passing in a
<js>"method"</js> URL parameter on a regular
+ * GET request.
+ * For example: <js>"?method=OPTIONS"</js>
+ *
+ * <p>
+ * Format is a comma-delimited list of HTTP method names that can be
passed in as a method parameter.
+ * Parameter name is case-insensitive.
+ * Use "*" to represent all methods.
+ * For backwards compatibility, "true" also means "*".
+ *
+ * <p>
+ * Note that per the <a class="doclink"
+ * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html">HTTP
specification</a>, special care should
+ * be taken when allowing non-safe (POST, PUT, DELETE) methods to be
invoked through GET requests.
+ *
+ * <p>
+ * Applicable to servlet class only.
+ *
+ * <p>
+ * Example: <js>"HEAD,OPTIONS"</js>
+ */
+ public static final String REST_allowMethodParam =
"RestServlet.allowMethodParam";
+
+ /**
+ * <b>Configuration property:</b> Enable <js>"body"</js> URL parameter.
+ *
+ * <ul>
+ * <li><b>Name:</b> <js>"RestServlet.allowBodyParam"</js>
+ * <li><b>Data type:</b> <code>Boolean</code>
+ * <li><b>Default:</b> <jk>true</jk>
+ * </ul>
+ *
+ * <p>
+ * When enabled, the HTTP body content on PUT and POST requests can be
passed in as text using the <js>"body"</js>
+ * URL parameter.
+ * For example: <js>"?body={name:'John%20Smith',age:45}"</js>
+ *
+ * <p>
+ * Parameter name is case-insensitive.
+ *
+ * <p>
+ * Useful for debugging PUT and POST methods using only a browser.
+ *
+ * <p>
+ * Applicable to servlet class only.
+ */
+ public static final String REST_allowBodyParam =
"RestServlet.allowBodyParam";
+
+ /**
+ * <b>Configuration property:</b> Render stack traces.
+ *
+ * <ul>
+ * <li><b>Name:</b>
<js>"RestServlet.renderResponseStackTraces"</js>
+ * <li><b>Data type:</b> <code>Boolean</code>
+ * <li><b>Default:</b> <jk>false</jk>
+ * </ul>
+ *
+ * <p>
+ * Render stack traces in HTTP response bodies when errors occur.
+ *
+ * <p>
+ * When enabled, Java stack traces will be rendered in the output
response.
+ * Useful for debugging, although allowing stack traces to be rendered
may cause security concerns.
+ *
+ * <p>
+ * Applicable to servlet class only.
+ */
+ public static final String REST_renderResponseStackTraces =
"RestServlet.renderResponseStackTraces";
+
+ /**
+ * <b>Configuration property:</b> Use stack trace hashes.
+ *
+ * <ul>
+ * <li><b>Name:</b> <js>"RestServlet.useStackTraceHashes"</js>
+ * <li><b>Data type:</b> <code>Boolean</code>
+ * <li><b>Default:</b> <jk>true</jk>
+ * </ul>
+ *
+ * <p>
+ * When enabled, the number of times an exception has occurred will be
determined based on stack trace hashsums,
+ * made available through the {@link RestException#getOccurrence()}
method.
+ *
+ * <p>
+ * Applicable to servlet class only.
+ */
+ public static final String REST_useStackTraceHashes =
"RestServlet.useStackTraceHashes";
+
+ /**
+ * <b>Configuration property:</b> Default character encoding.
+ *
+ * <ul>
+ * <li><b>Name:</b> <js>"RestServlet.defaultCharset"</js>
+ * <li><b>Data type:</b> <code>String</code>
+ * <li><b>Default:</b> <js>"utf-8"</js>
+ * </ul>
+ *
+ * <p>
+ * The default character encoding for the request and response if not
specified on the request.
+ *
+ * <p>
+ * Applicable to servlet class and methods.
+ */
+ public static final String REST_defaultCharset =
"RestServlet.defaultCharset";
+
+ /**
+ * <b>Configuration property:</b> Expected format of request
parameters.
+ *
+ * <ul>
+ * <li><b>Name:</b> <js>"RestServlet.paramFormat"</js>
+ * <li><b>Data type:</b> <code>String</code>
+ * <li><b>Default:</b> <js>"UON"</js>
+ * </ul>
+ * <p>
+ * Possible values:
+ * <ul class='spaced-list'>
+ * <li>
+ * <js>"UON"</js> - URL-Encoded Object Notation.
+ * <br>This notation allows for request parameters to
contain arbitrarily complex POJOs.
+ * <li>
+ * <js>"PLAIN"</js> - Plain text.
+ * <br>This treats request parameters as plain text.
+ * <br>Only POJOs directly convertible from <l>Strings</l>
can be represented in parameters when using this
+ * mode.
+ * </ul>
+ *
+ * <p>
+ * Note that the parameter value <js>"(foo)"</js> is interpreted as
<js>"(foo)"</js> when using plain mode, but
+ * <js>"foo"</js> when using UON mode.
+ *
+ * <p>
+ * The format can also be specified per-parameter using the {@link
FormData#format() @FormData.format()} and
+ * {@link Query#format() @Query.format()} annotations.
+ *
+ * <p>
+ * Applicable to servlet class and methods.
+ */
+ public static final String REST_paramFormat = "RestServlet.paramFormat";
+
+ /**
+ * <b>Configuration property:</b> REST resource resolver.
+ *
+ * <ul>
+ * <li><b>Name:</b> <js>"RestServlet.resourceResolver"</js>
+ * <li><b>Data type:</b> <code>Class</code> or {@link
RestResourceResolver}
+ * <li><b>Default:</b> <jk>null</jk>
+ * </ul>
+ *
+ * <p>
+ * The resource resolver used to instantiate REST resource classes.
+ *
+ * <p>
+ * Applicable to servlet class.
+ * <br>Can be passed in through servlet context.
+ */
+ public static final String REST_resourceResolver =
"RestServlet.resourceResolver";
+
+
+ private final Object resource;
+ final RestConfig config;
+ private final boolean
+ allowHeaderParams,
+ allowBodyParam,
+ renderResponseStackTraces,
+ useStackTraceHashes;
+ private final String
+ defaultCharset,
+ paramFormat,
+ clientVersionHeader,
+ fullPath,
+ contextPath,
+ htmlHeader,
+ htmlNav,
+ htmlAside,
+ htmlStyle,
+ htmlStylesheet,
+ htmlScript,
+ htmlFooter,
+ htmlNoResultsMessage;
+ private final String[]
+ htmlLinks;
+ private final boolean htmlNoWrap;
+ private final HtmlDocTemplate htmlTemplate;
+ private final Map<String,Widget> htmlWidgets;
+
+ private final Set<String> allowMethodParams;
+
+ private final ObjectMap properties;
+ private final Class<?>[]
+ beanFilters,
+ pojoSwaps;
+ private final Map<Class<?>,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<String,String> defaultRequestHeaders;
+ private final Map<String,Object> defaultResponseHeaders;
+ private final BeanContext beanContext;
+ private final RestConverter[] converters;
+ private final RestGuard[] guards;
+ private final ResponseHandler[] responseHandlers;
+ private final MimetypesFileTypeMap mimetypesFileTypeMap;
+ private final StreamResource favIcon;
+ private final Map<String,String> staticFilesMap;
+ private final String[] staticFilesPrefixes;
+ private final MessageBundle msgs;
+ private final ConfigFile configFile;
+ private final VarResolver varResolver;
+ private final Map<String,CallRouter> callRouters;
+ private final Map<String,CallMethod> callMethods;
+ private final Map<String,RestContext> 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<String,StreamResource> staticFilesCache = new
ConcurrentHashMap<String,StreamResource>();
+
+ private final ResourceFinder resourceFinder;
+ private final ConcurrentHashMap<Integer,AtomicInteger> stackTraceHashes
= new ConcurrentHashMap<Integer,AtomicInteger>();
+
+
+ /**
+ * Constructor.
+ *
+ * @param resource The resource class (a class annotated with {@link
RestResource @RestResource}).
+ * @param servletContext
+ * The servlet context object.
+ * Can be <jk>null</jk> 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, servletContext,
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.favIcon = b.favIcon;
+ this.staticFilesMap =
Collections.unmodifiableMap(b.staticFilesMap);
+ this.staticFilesPrefixes = b.staticFilesPrefixes;
+ this.msgs = b.messageBundle;
+ this.childResources = Collections.synchronizedMap(new
LinkedHashMap<String,RestContext>()); // Not unmodifiable on purpose so that
children can be replaced.
+ this.logger = b.logger;
+ this.fullPath = b.fullPath;
+ this.contextPath = nullIfEmpty(b.contextPath);
+
+ this.htmlWidgets =
Collections.unmodifiableMap(b.htmlWidgets);
+ this.htmlHeader = b.htmlHeader;
+ this.htmlLinks = b.htmlLinks;
+ this.htmlNav = b.htmlNav;
+ this.htmlAside = b.htmlAside;
+ this.htmlStyle = b.htmlStyle;
+ this.htmlStylesheet = b.htmlStylesheet;
+ this.htmlScript = b.htmlScript;
+ this.htmlFooter = b.htmlFooter;
+ this.htmlNoWrap = b.htmlNoWrap;
+ this.htmlNoResultsMessage = b.htmlNoResultsMessage;
+ this.htmlTemplate = b.htmlTemplate;
+
+
//----------------------------------------------------------------------------------------------------
+ // Initialize the child resources.
+ // Done after initializing fields above since we pass
this object to the child resources.
+
//----------------------------------------------------------------------------------------------------
+ List<String> methodsFound = new LinkedList<String>();
// Temporary to help debug transient duplicate method issue.
+ Map<String,CallRouter.Builder> routers = new
LinkedHashMap<String,CallRouter.Builder>();
+ Map<String,CallMethod> _javaRestMethods = new
LinkedHashMap<String,CallMethod>();
+ Map<String,Method>
+ _startCallMethods = new
LinkedHashMap<String,Method>(),
+ _preCallMethods = new
LinkedHashMap<String,Method>(),
+ _postCallMethods = new
LinkedHashMap<String,Method>(),
+ _endCallMethods = new
LinkedHashMap<String,Method>(),
+ _postInitMethods = new
LinkedHashMap<String,Method>(),
+ _postInitChildFirstMethods = new
LinkedHashMap<String,Method>(),
+ _destroyMethods = new
LinkedHashMap<String,Method>();
+ List<RestParam[]>
+ _preCallMethodParams = new
ArrayList<RestParam[]>(),
+ _postCallMethodParams = new
ArrayList<RestParam[]>();
+ List<Class<?>[]>
+ _startCallMethodParams = new
ArrayList<Class<?>[]>(),
+ _endCallMethodParams = new
ArrayList<Class<?>[]>(),
+ _postInitMethodParams = new
ArrayList<Class<?>[]>(),
+ _postInitChildFirstMethodParams = new
ArrayList<Class<?>[]>(),
+ _destroyMethodParams = new
ArrayList<Class<?>[]>();
+
+ 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<String,Method> 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<String,CallRouter> _callRouters = new
LinkedHashMap<String,CallRouter>();
+ 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<String,Object> p =
(Pair<String,Object>)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<String, CallRouter.Builder>
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<Class<?>,RestParam> paramResolvers = new
HashMap<Class<?>,RestParam>();
+ SerializerGroup serializers;
+ ParserGroup parsers;
+ UrlEncodingSerializer urlEncodingSerializer;
+ UrlEncodingParser urlEncodingParser;
+ EncoderGroup encoders;
+ String clientVersionHeader = "", defaultCharset, paramFormat,
htmlHeader, htmlNav, htmlAside, htmlStyle,
+ htmlStylesheet, htmlScript, htmlFooter,
htmlNoResultsMessage;
+ String[] htmlLinks;
+ boolean htmlNoWrap;
+ HtmlDocTemplate htmlTemplate;
+
+ List<MediaType> supportedContentTypes, supportedAcceptTypes;
+ Map<String,String> defaultRequestHeaders = new
TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
+ Map<String,Object> defaultResponseHeaders;
+ BeanContext beanContext;
+ List<RestConverter> converters = new ArrayList<RestConverter>();
+ List<RestGuard> guards = new ArrayList<RestGuard>();
+ List<ResponseHandler> responseHandlers = new
ArrayList<ResponseHandler>();
+ MimetypesFileTypeMap mimetypesFileTypeMap;
+ StreamResource favIcon;
+ Map<String,String> staticFilesMap;
+ String[] staticFilesPrefixes;
+ MessageBundle messageBundle;
+ Set<String> allowMethodParams = new LinkedHashSet<String>();
+ RestLogger logger;
+ String fullPath;
+ Map<String,Widget> htmlWidgets;
+ Object resourceResolver;
+ String contextPath;
+
+ @SuppressWarnings("unchecked")
+ private Builder(Object resource, ServletContext ctx, RestConfig
sc) throws Exception {
+
+ PropertyStore ps = sc.createPropertyStore();
+
+ LinkedHashMap<Class<?>,RestResource>
restResourceAnnotationsChildFirst = findAnnotationsMap(RestResource.class,
resource.getClass());
+
+ allowHeaderParams =
ps.getProperty(REST_allowHeaderParams, boolean.class, true);
+ allowBodyParam = ps.getProperty(REST_allowBodyParam,
boolean.class, true);
+ renderResponseStackTraces =
ps.getProperty(REST_renderResponseStackTraces, boolean.class, false);
+ useStackTraceHashes =
ps.getProperty(REST_useStackTraceHashes, boolean.class, true);
+ defaultCharset = ps.getProperty(REST_defaultCharset,
String.class, "utf-8");
+ paramFormat = ps.getProperty(REST_paramFormat,
String.class, "");
+ resourceResolver =
ps.getProperty(REST_resourceResolver, Object.class, ctx == null ? null :
ctx.getAttribute(REST_resourceResolver));
+ if (resourceResolver == null)
+ resourceResolver = sc.resourceResolver;
+
+ for (String m :
split(ps.getProperty(REST_allowMethodParam, String.class, "")))
+ if (m.equals("true")) // For backwards
compatibility when this was a boolean field.
+ allowMethodParams.add("*");
+ else
+ allowMethodParams.add(m.toUpperCase());
+
+ 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<Class<?>,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<String,Object>(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();
+
+ if (sc.favIcon != null) {
+ Object o = sc.favIcon;
+ InputStream is = null;
+ if (o instanceof Pair) {
+ Pair<Class<?>,String> p =
(Pair<Class<?>,String>)o;
+ is =
ReflectionUtils.getResource(p.first(), vr.resolve(p.second()));
+ } else {
+ is = toInputStream(o);
+ }
+ if (is != null)
+ favIcon = new
StreamResource(MediaType.forString("image/x-icon"), is);
+ }
+
+ staticFilesMap = new LinkedHashMap<String,String>();
+ if (sc.staticFiles != null) {
+ for (Object o : sc.staticFiles) {
+ if (o instanceof Pair) {
+ Pair<Class<?>,String> p =
(Pair<Class<?>,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;
+
+ this.htmlWidgets = new LinkedHashMap<String,Widget>();
+ for (Class<? extends Widget> wc : sc.htmlWidgets) {
+ Widget w = resolve(resource, Widget.class, wc);
+ this.htmlWidgets.put(w.getName(), w);
+ }
+
+ htmlHeader = sc.htmlHeader;
+ htmlLinks = sc.htmlLinks;
+ htmlNav = sc.htmlNav;
+ htmlAside = sc.htmlAside;
+ htmlStyle = sc.htmlStyle;
+ htmlStylesheet = sc.htmlStylesheet;
+ htmlScript = sc.htmlScript;
+ htmlFooter = sc.htmlFooter;
+ htmlNoWrap = sc.htmlNoWrap;
+ htmlNoResultsMessage = sc.htmlNoResultsMessage;
+ htmlTemplate = resolve(resource, HtmlDocTemplate.class,
sc.htmlTemplate);
+ }
+ }
+
+ /**
+ * Returns the resource resolver associated with this context.
+ *
+ * <p>
+ * The resource resolver is used for instantiating child resource
classes.
+ *
+ * <p>
+ * 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.
+ *
+ * <p>
+ * 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.
+ *
+ * <h6 class='figure'>Example:</h6>
+ * <p class='bcode'>
+ * <ja>@RestResource</ja>(
+ * messages=<js>"nls/Messages"</js>,
+ * properties={
+ *
<ja>@Property</ja>(name=<js>"title"</js>,value=<js>"$L{title}"</js>), <jc>//
Localized variable in Messages.properties</jc>
+ *
<ja>@Property</ja>(name=<js>"javaVendor"</js>,value=<js>"$S{java.vendor,Oracle}"</js>),
<jc>// System property with default value</jc>
+ *
<ja>@Property</ja>(name=<js>"foo"</js>,value=<js>"bar"</js>),
+ *
<ja>@Property</ja>(name=<js>"bar"</js>,value=<js>"baz"</js>),
+ *
<ja>@Property</ja>(name=<js>"v1"</js>,value=<js>"$R{foo}"</js>), <jc>//
Request variable. value="bar"</jc>
+ *
<ja>@Property</ja>(name=<js>"v2"</js>,value=<js>"$R{$R{foo}}"</js>) <jc>//
Nested request variable. value="baz"</jc>
+ * }
+ * )
+ * <jk>public class</jk> MyRestResource <jk>extends</jk>
RestServletDefault {
+ * </p>
+ *
+ * <p>
+ * A typical usage pattern involves using variables inside the {@link
HtmlDoc} annotation:
+ * <p class='bcode'>
+ * <ja>@RestMethod</ja>(
+ * name=<js>"GET"</js>, path=<js>"/{name}/*"</js>,
+ * htmldoc=@HtmlDoc(
+ * links={
+ * <js>"up: $R{requestParentURI}"</js>,
+ * <js>"options:
servlet:/?method=OPTIONS"</js>,
+ * <js>"editLevel:
servlet:/editLevel?logger=$A{attribute.name, OFF}"</js>
+ * }
+ * header={
+ *
<js>"<h1>$L{MyLocalizedPageTitle}</h1>"</js>
+ * },
+ * aside={
+ * <js>"$F{resources/AsideText.html}"</js>
+ * }
+ * )
+ * )
+ * <jk>public</jk> LoggerEntry getLogger(RestRequest req,
<ja>@Path</ja> String name) <jk>throws</jk> Exception {
+ * </p>
+ *
+ * <p>
+ * The following is the default list of supported variables:
+ * <ul>
+ * <li><code>$C{key[,defaultValue]}</code> - Config file entry.
See {@link ConfigFileVar}.
+ * <li><code>$E{envVar[,defaultValue]}</code> - Environment
variable. See {@link EnvVariablesVar}.
+ * <li><code>$F{path[,defaultValue]}</code> - File resource. See
{@link FileVar}.
+ * <li><code>$I{name[,defaultValue]}</code> - Servlet init
parameter. See {@link ServletInitParamVar}.
+ * <li><code>$L{key[,args...]}</code> - Localized message. See
{@link LocalizationVar}.
+ * <li><code>$R{key[,args...]}</code> - Request variable. See
{@link RequestVar}.
+ * <li><code>$S{systemProperty[,defaultValue]}</code> - System
property. See {@link SystemPropertiesVar}.
+ * <li><code>$SA{contentType,key[,defaultValue]}</code> -
Serialized request attribute. See {@link SerializedRequestAttrVar}.
+ * <li><code>$U{uri}</code> - URI resolver. See {@link UrlVar}.
+ * <li><code>$UE{uriPart}</code> - URL-Encoder. See {@link
UrlEncodeVar}.
+ * <li><code>$W{widgetName}</code> - HTML widget variable. See
{@link WidgetVar}.
+ * </ul>
+ *
+ * <p>
+ * The following syntax variables are also provided:
+ * <ul>
+ * <li><code>$IF{booleanArg,thenValue[,elseValue]}</code> -
If/else variable. See {@link IfVar}.
+ *
<li><code>$SW{stringArg(,pattern,thenValue)+[,elseValue]}</code> - Switch
variable. See {@link SwitchVar}.
+ * </ul>
+ *
+ * <p>
+ * 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
+ * <code>Resource</code> class.
+ *
+ * @return The var resolver in use by this resource.
+ */
+ public VarResolver getVarResolver() {
+ return varResolver;
+ }
+
+ /**
+ * Returns the config file associated with this servlet.
+ *
+ * <p>
+ * The config file is identified via one of the following:
+ * <ul>
+ * <li>{@link RestResource#config() @RestResource.config()}
annotation.
+ * <li>{@link RestConfig#setConfigFile(ConfigFile)} method.
+ * </ul>
+ *
+ * @return The resolving config file associated with this servlet.
Never <jk>null</jk>.
+ */
+ public ConfigFile getConfigFile() {
+ return configFile;
+ }
+
+ /**
+ * Resolve a static resource file.
+ *
+ * <p>
+ * The location of static resources are defined via one of the
following:
+ * <ul>
+ * <li>{@link RestResource#staticFiles()
@RestResource.staticFiles()} annotation.
+ * <li>{@link RestConfig#addStaticFiles(Class, String)} method.
+ * </ul>
+ *
+ * @param pathInfo The unencoded path info.
+ * @return The resource, or <jk>null</jk> 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<String,String> 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.
+ *
+ * <p>
+ * If the resource cannot be found in the classpath, then an attempt is
made to look in the JVM working directory.
+ *
+ * <p>
+ * If the <code>locale</code> is specified, then we look for resources
whose name matches that locale.
+ * For example, if looking for the resource <js>"MyResource.txt"</js>
for the Japanese locale, we will look for
+ * files in the following order:
+ * <ol>
+ * <li><js>"MyResource_ja_JP.txt"</js>
+ * <li><js>"MyResource_ja.txt"</js>
+ * <li><js>"MyResource.txt"</js>
+ * </ol>
+ *
+ * @param name The resource name.
+ * @param locale Optional locale.
+ * @return An input stream of the resource, or <jk>null</jk> 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 <jk>null</jk> 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.
+ *
+ * <p>
+ * 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.
<js>"text/json"</js>)
+ * @param name The resource name (e.g. "htdocs/styles.css").
+ * @param locale Optional locale.
+ * @return The parsed resource, or <jk>null</jk> 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> T getResource(Class<T> 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.
+ *
+ * <p>
+ * If path is not specified, returns <js>"/"</js>.
+ *
+ * <p>
+ * Path always starts with <js>"/"</js>.
+ *
+ * @return The servlet path.
+ */
+ public String getPath() {
+ return fullPath;
+ }
+
+ /**
+ * The HTML page header contents.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#header()} annotation or {@link
RestConfig#setHtmlHeader(String)} method.
+ *
+ * @return The HTML page header contents.
+ */
+ public String getHtmlHeader() {
+ return htmlHeader;
+ }
+
+ /**
+ * The HTML page nav section links.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#links()} annotation or {@link
RestConfig#setHtmlLinks(String[])} method.
+ *
+ * @return The HTML page nav section links.
+ */
+ public String[] getHtmlLinks() {
+ return htmlLinks;
+ }
+
+ /**
+ * The HTML page nav section contents.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#nav()} annotation or {@link
RestConfig#setHtmlNav(String)} method.
+ *
+ * @return The HTML page nav section contents.
+ */
+ public String getHtmlNav() {
+ return htmlNav;
+ }
+
+ /**
+ * The HTML page aside section contents.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#aside()} annotation or {@link
RestConfig#setHtmlAside(String)} method.
+ *
+ * @return The HTML page aside section contents.
+ */
+ public String getHtmlAside() {
+ return htmlAside;
+ }
+
+ /**
+ * The HTML page footer section contents.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#footer()} annotation or {@link
RestConfig#setHtmlFooter(String)} method.
+ *
+ * @return The HTML page footer section contents.
+ */
+ public String getHtmlFooter() {
+ return htmlFooter;
+ }
+
+ /**
+ * The HTML page stylesheet URL.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#stylesheet()} annotation or {@link
RestConfig#setHtmlStylesheet(String)} method.
+ *
+ * @return The HTML page CSS URL.
+ */
+ public String getHtmlStylesheet() {
+ return htmlStylesheet;
+ }
+
+ /**
+ * The HTML page CSS contents.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#style()} annotation or {@link
RestConfig#setHtmlStyle(String)} method.
+ *
+ * @return The HTML page CSS contents.
+ */
+ public String getHtmlStyle() {
+ return htmlStyle;
+ }
+
+ /**
+ * The HTML page Javascript contents.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#script()} annotation or {@link
RestConfig#setHtmlScript(String)} method.
+ *
+ * @return The HTML page Javascript contents.
+ */
+ public String getHtmlScript() {
+ return htmlScript;
+ }
+
+ /**
+ * The HTML page nowrap setting.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#nowrap()} annotation or {@link
RestConfig#setHtmlNoWrap(boolean)} method.
+ *
+ * @return The HTML page nowrap setting.
+ */
+ public boolean getHtmlNoWrap() {
+ return htmlNoWrap;
+ }
+
+ /**
+ * The HTML page template.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#template()} annotation or {@link
RestConfig#setHtmlTemplate(Class)} method.
+ *
+ * @return The HTML page template.
+ */
+ public HtmlDocTemplate getHtmlTemplate() {
+ return htmlTemplate;
+ }
+
+ /**
+ * The HTML page no-results message.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#noResultsMessage()} annotation or
{@link RestConfig#setHtmlNoResultsMessage(String)}
+ * method.
+ *
+ * @return The HTML page no-results message.
+ */
+ public String getHtmlNoResultsMessage() {
+ return htmlNoResultsMessage;
+ }
+
+ /**
+ * The widgets used for resolving <js>"$W{...}"<js> variables.
+ *
+ * <p>
+ * Defined by the {@link HtmlDoc#widgets()} annotation or {@link
RestConfig#addHtmlWidget(Class)} method.
+ *
+ * @return The var resolver widgets as a map with keys being the name
returned by {@link Widget#getName()}.
+ */
+ public Map<String,Widget> getHtmlWidgets() {
+ return htmlWidgets;
+ }
+
+ /**
+ * Returns the logger to use for this resource.
+ *
+ * <p>
+ * The logger for a resource is defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#logger() @RestResource.logger()}
annotation.
+ * <li>{@link RestConfig#setLogger(Class)}/{@link
RestConfig#setLogger(RestLogger)} methods.
+ * </ul>
+ *
+ * @return The logger to use for this resource. Never <jk>null</jk>.
+ */
+ public RestLogger getLogger() {
+ return logger;
+ }
+
+ /**
+ * Returns the resource bundle used by this resource.
+ *
+ * <p>
+ * The resource bundle is defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#messages() @RestResource.messages()}
annotation.
+ * </ul>
+ *
+ * @return The resource bundle for this resource. Never <jk>null</jk>.
+ */
+ public MessageBundle getMessages() {
+ return msgs;
+ }
+
+ /**
+ * Returns the REST information provider used by this resource.
+ *
+ * <p>
+ * The information provider is defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#infoProvider()
@RestResource.infoProvider()} annotation.
+ * <li>{@link RestConfig#setInfoProvider(Class)}/{@link
RestConfig#setInfoProvider(RestInfoProvider)} methods.
+ * </ul>
+ *
+ * @return The information provider for this resource. Never
<jk>null</jk>.
+ */
+ public RestInfoProvider getInfoProvider() {
+ return infoProvider;
+ }
+
+ /**
+ * Returns the REST call handler used by this resource.
+ *
+ * <p>
+ * The call handler is defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#callHandler()
@RestResource.callHandler()} annotation.
+ * <li>{@link RestConfig#setCallHandler(Class)}/{@link
RestConfig#setCallHandler(RestCallHandler)} methods.
+ * </ul>
+ *
+ * @return The call handler for this resource. Never <jk>null</jk>.
+ */
+ 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<String,CallRouter> getCallRouters() {
+ return callRouters;
+ }
+
+ /**
+ * Returns the resource object.
+ *
+ * <p>
+ * 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 <jk>null</jk>.
+ */
+ public Object getResource() {
+ return resource;
+ }
+
+ /**
+ * Returns the resource object as a {@link RestServlet}.
+ *
+ * @return
+ * The resource object cast to {@link RestServlet}, or
<jk>null</jk> 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).
+ *
+ * <p>
+ * 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 <jk>null</jk> 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.
+ *
+ * <p>
+ * Properties at the class level are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#properties()
@RestResource.properties()} annotation.
+ * <li>{@link RestConfig#setProperty(String, Object)}/{@link
RestConfig#setProperties(Map)} methods.
+ * </ul>
+ *
+ * <h5 class='section'>Notes:</h5>
+ * <ul>
+ * <li>The returned {@code Map} is mutable. Therefore, subclasses
are free to override
+ * or set additional initialization parameters in their {@code
init()} method.
+ * </ul>
+ *
+ * @return The resource properties as an {@link ObjectMap}.
+ */
+ public ObjectMap getProperties() {
+ return properties;
+ }
+
+ /**
+ * Returns the serializers registered with this resource.
+ *
+ * <p>
+ * Serializers at the class level are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#serializers()
@RestResource.serializers()} annotation.
+ * <li>{@link RestConfig#addSerializers(Class...)}/{@link
RestConfig#addSerializers(Serializer...)} methods.
+ * </ul>
+ *
+ * @return The serializers registered with this resource.
+ */
+ public SerializerGroup getSerializers() {
+ return serializers;
+ }
+
+ /**
+ * Returns the parsers registered with this resource.
+ *
+ * <p>
+ * Parsers at the class level are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#parsers() @RestResource.parsers()}
annotation.
+ * <li>{@link RestConfig#addParsers(Class...)}/{@link
RestConfig#addParsers(Parser...)} methods.
+ * </ul>
+ *
+ * @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 <jk>null</jk> 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<String,RestContext> 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
<code>0</code> if <code>stackTraceHashes</code>
+ * 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 #REST_renderResponseStackTraces}
setting.
+ *
+ * @return The value of the {@link #REST_renderResponseStackTraces}
setting.
+ */
+ protected boolean isRenderResponseStackTraces() {
+ return renderResponseStackTraces;
+ }
+
+ /**
+ * Returns the value of the {@link #REST_allowHeaderParams} setting.
+ *
+ * @return The value of the {@link #REST_allowHeaderParams} setting.
+ */
+ protected boolean isAllowHeaderParams() {
+ return allowHeaderParams;
+ }
+
+ /**
+ * Returns the value of the {@link #REST_allowBodyParam} setting.
+ *
+ * @return The value of the {@link #REST_allowBodyParam} setting.
+ */
+ protected boolean isAllowBodyParam() {
+ return allowBodyParam;
+ }
+
+ /**
+ * Returns the value of the {@link #REST_defaultCharset} setting.
+ *
+ * @return The value of the {@link #REST_defaultCharset} setting.
+ */
+ protected String getDefaultCharset() {
+ return defaultCharset;
+ }
+
+ /**
+ * Returns the value of the {@link #REST_paramFormat} setting.
+ *
+ * @return The value of the {@link #REST_paramFormat} setting.
+ */
+ protected String getParamFormat() {
+ return paramFormat;
+ }
+
+ /**
+ * Returns the name of the client version header name used by this
resource.
+ *
+ * <p>
+ * The client version header is the name of the HTTP header on requests
that identify a client version.
+ *
+ * <p>
+ * The client version header is defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#clientVersionHeader()
@RestResource.clientVersion()} annotation.
+ * </ul>
+ *
+ * @return The name of the client version header used by this resource.
Never <jk>null</jk>.
+ */
+ protected String getClientVersionHeader() {
+ return clientVersionHeader;
+ }
+
+ /**
+ * Returns <jk>true</jk> if the specified <code>Method</code> GET
parameter value can be used to override
+ * the method name in the HTTP header.
+ *
+ * @param m The method name, upper-cased.
+ * @return <jk>true</jk> 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.
+ *
+ * <p>
+ * Bean filters at the class level are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#beanFilters()
@RestResource.beanFilters()} annotation.
+ * <li>{@link RestConfig#addBeanFilters(Class...)} method.
+ * </ul>
+ *
+ * @return The bean filters associated with this resource. Never
<jk>null</jk>.
+ */
+ protected Class<?>[] getBeanFilters() {
+ return beanFilters;
+ }
+
+ /**
+ * Returns the POJO swaps associated with this resource.
+ *
+ * <p>
+ * POJO swaps at the class level are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#pojoSwaps() @RestResource.pojoSwaps()}
annotation.
+ * <li>{@link RestConfig#addPojoSwaps(Class...)} method.
+ * </ul>
+ *
+ * @return The POJO swaps associated with this resource. Never
<jk>null</jk>.
+ */
+ 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 <ja>@RestMethodPre</ja> or
<ja>@RestMethodPost</ja>.
+ * @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 <jk>null</jk>.
+ */
+ protected UrlEncodingParser getUrlEncodingParser() {
+ return urlEncodingParser;
+ }
+
+ /**
+ * Returns the URL-encoding serializer associated with this resource.
+ *
+ * @return The URL-encoding serializer associated with this resource.
Never <jk>null</jk>.
+ */
+ protected UrlEncodingSerializer getUrlEncodingSerializer() {
+ return urlEncodingSerializer;
+ }
+
+ /**
+ * Returns the encoders associated with this resource.
+ *
+ * <p>
+ * Encoders are used to provide various types of encoding such as
<code>gzip</code> encoding.
+ *
+ * <p>
+ * Encoders at the class level are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#encoders() @RestResource.encoders()}
annotation.
+ * <li>{@link RestConfig#addEncoders(Class...)}/{@link
RestConfig#addEncoders(org.apache.juneau.encoders.Encoder...)}
+ * methods.
+ * </ul>
+ *
+ * @return The encoders associated with this resource. Never
<jk>null</jk>.
+ */
+ protected EncoderGroup getEncoders() {
+ return encoders;
+ }
+
+ /**
+ * Returns the explicit list of supported accept types for this
resource.
+ *
+ * <p>
+ * 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 <code>Accept</code> header values for this
resource. Never <jk>null</jk>.
+ */
+ protected MediaType[] getSupportedAcceptTypes() {
+ return supportedAcceptTypes;
+ }
+
+ /**
+ * Returns the explicit list of supported content types for this
resource.
+ *
+ * <p>
+ * 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 <code>Content-Type</code> header values for
this resource. Never <jk>null</jk>.
+ */
+ protected MediaType[] getSupportedContentTypes() {
+ return supportedContentTypes;
+ }
+
+ /**
+ * Returns the default request headers for this resource.
+ *
+ * <p>
+ * These are headers automatically added to requests if not present.
+ *
+ * <p>
+ * Default request headers are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#defaultRequestHeaders()
@RestResource.defaultRequestHeaders()} annotation.
+ * <li>{@link RestConfig#addDefaultRequestHeader(String,
Object)}/{@link RestConfig#addDefaultRequestHeaders(String...)} methods.
+ * </ul>
+ *
+ * @return The default request headers for this resource. Never
<jk>null</jk>.
+ */
+ protected Map<String,String> getDefaultRequestHeaders() {
+ return defaultRequestHeaders;
+ }
+
+ /**
+ * Returns the default response headers for this resource.
+ *
+ * <p>
+ * These are headers automatically added to responses if not otherwise
specified during the request.
+ *
+ * <p>
+ * Default response headers are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#defaultResponseHeaders()
@RestResource.defaultResponseHeaders()} annotation.
+ * <li>{@link RestConfig#addDefaultResponseHeader(String,
Object)}/{@link RestConfig#addDefaultResponseHeaders(String...)}
+ * methods.
+ * </ul>
+ *
+ * @return The default response headers for this resource. Never
<jk>null</jk>.
+ */
+ public Map<String,Object> getDefaultResponseHeaders() {
+ return defaultResponseHeaders;
+ }
+
+ /**
+ * Returns the converters associated with this resource at the class
level.
+ *
+ * <p>
+ * Converters are used to 'convert' POJOs from one form to another
before being passed of to the response handlers.
+ *
+ * <p>
+ * Converters at the class level are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#converters()
@RestResource.converters()} annotation.
+ * <li>{@link RestConfig#addConverters(Class...)}/{@link
RestConfig#addConverters(RestConverter...)} methods.
+ * </ul>
+ *
+ * @return The converters associated with this resource. Never
<jk>null</jk>.
+ */
+ protected RestConverter[] getConverters() {
+ return converters;
+ }
+
+ /**
+ * Returns the guards associated with this resource at the class level.
+ *
+ * <p>
+ * Guards are used to restrict access to resources.
+ *
+ * <p>
+ * Guards at the class level are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#guards() @RestResource.guards()}
annotation.
+ * <li>{@link RestConfig#addGuards(Class...)}/{@link
RestConfig#addGuards(RestGuard...)} methods.
+ * </ul>
+ *
+ * @return The guards associated with this resource. Never
<jk>null</jk>.
+ */
+ protected RestGuard[] getGuards() {
+ return guards;
+ }
+
+ /**
+ * Returns the response handlers associated with this resource.
+ *
+ * <p>
+ * Response handlers are used to convert POJOs returned by REST Java
methods into actual HTTP responses.
+ *
+ * <p>
+ * Response handlers are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#responseHandlers()
@RestResource.responseHandlers()} annotation.
+ * <li>{@link RestConfig#addResponseHandlers(Class...)}/{@link
RestConfig#addResponseHandlers(ResponseHandler...)}
+ * methods.
+ * </ul>
+ *
+ * @return The response handlers associated with this resource. Never
<jk>null</jk>.
+ */
+ protected ResponseHandler[] getResponseHandlers() {
+ return responseHandlers;
+ }
+
+ /**
+ * Returns the media type for the specified file name.
+ *
+ * <p>
+ * 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 <jk>null</jk> if it could not be
determined.
+ */
+ protected String getMediaTypeForName(String name) {
+ return mimetypesFileTypeMap.getContentType(name);
+ }
+
+ /**
+ * Returns the favicon of the resource.
+ *
+ * <p>
+ * This is the icon served up under <js>"/favicon.ico"</jk> recognized
by browsers.
+ *
+ * <p>
+ * The favicon is defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#favicon() @RestResource.favicon()}
annotation.
+ * <li>{@link RestConfig#setFavIcon(Object)}/{@link
RestConfig#setFavIcon(Class, String)} methods.
+ * </ul>
+ *
+ * @return The favicon of this resource. Can be <jk>null</jk>.
+ */
+ protected StreamResource getFavIcon() {
+ return favIcon;
+ }
+
+ /**
+ * Returns <jk>true</jk> if the specified path refers to a static file.
+ *
+ * <p>
+ * Static files are files pulled from the classpath and served up
directly to the browser.
+ *
+ * <p>
+ * Static files are defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#staticFiles()
@RestResource.staticFiles()} annotation.
+ * <li>{@link RestConfig#addStaticFiles(Class, String)} method.
+ * </ul>
+ *
+ * @param p The URL path remainder after the servlet match.
+ * @return <jk>true</jk> 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.
+ *
+ * <p>
+ * These are the methods annotated with the {@link RestMethod
@RestMethod} annotation.
+ *
+ * @return A map of Java method names to call method objects.
+ */
+ protected Map<String,CallMethod> 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 <jk>true</jk> if this resource has any child resources
associated with it.
+ *
+ * @return <jk>true</jk> 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 <jk>null</jk> 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
<jk>null</jk> 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<T> and either casts or
constructs a T.
+ */
+ private static <T> T resolve(Object outer, Class<T> 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);
+ }
+ }
+}
Propchange:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConverter.java
==============================================================================
---
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConverter.java
(added)
+++
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConverter.java
Fri Sep 8 23:25:34 2017
@@ -0,0 +1,83 @@
+//
***************************************************************************************************************************
+// * 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 org.apache.juneau.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.converters.*;
+import org.apache.juneau.serializer.*;
+
+/**
+ * REST method response converter.
+ *
+ * <p>
+ * Implements a filter mechanism for REST method calls that allows response
objects to be converted to some other POJO
+ * after invocation of the REST method.
+ *
+ * <p>
+ * Converters are associated with REST methods through the {@link
RestMethod#converters()} annotation.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * <jk>public class</jk> RequestEchoResource <jk>extends</jk> RestServlet {
+ *
+ * <jc>// GET request handler</jc>
+ * <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/*"</js>,
converters={Queryable.<jk>class</jk>,Traversable.<jk>class</jk>})
+ * <jk>public</jk> HttpServletRequest doGet(RestRequest req) {
+ * res.setTitle(<js>"Contents of HttpServletRequest
object"</js>);
+ * <jk>return</jk> req;
+ * }
+ * }
+ * </p>
+ *
+ * <p>
+ * Converters can also be associated at the servlet level using the {@link
RestResource#converters()} annotation.
+ * Applying converters at the resource level is equivalent to applying
converters to each resource method individually.
+ *
+ * <h6 class='topic'>How to implement</h6>
+ *
+ * Implementers should simply implement the {@link #convert(RestRequest,
Object, ClassMeta)} and return back a
+ * 'converted' object.
+ * It's up to the implementer to decide what this means.
+ *
+ * <p>
+ * Converters must implement a no-args constructor.
+ *
+ * <h6 class='topic'>Predefined converters</h6>
+ *
+ * The following converters are available by default.
+ * <ul class='spaced-list'>
+ * <li>
+ * {@link Traversable} - Allows URL additional path info to
address individual elements in a POJO tree.
+ * <li>
+ * {@link Queryable} - Allows query/view/sort functions to be
performed on POJOs.
+ * <li>
+ * {@link Introspectable} - Allows Java public methods to be
invoked on the returned POJOs.
+ * </ul>
+ */
+public interface RestConverter {
+
+ /**
+ * Performs post-call conversion on the specified response object.
+ *
+ * @param req The servlet request.
+ * @param res The response object set by the REST method through the
{@link RestResponse#setOutput(Object)} method.
+ * @param cm
+ * The {@link ClassMeta} on the object from the bean context of
the servlet.
+ * Can be used to check if the object has any filters.
+ * @return The converted object.
+ * @throws RestException Thrown if any errors occur during conversion.
+ * @throws SerializeException
+ */
+ public Object convert(RestRequest req, Object res, ClassMeta<?> cm)
throws RestException, SerializeException;
+}
Propchange:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConverter.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestException.java
==============================================================================
---
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestException.java
(added)
+++
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestException.java
Fri Sep 8 23:25:34 2017
@@ -0,0 +1,165 @@
+//
***************************************************************************************************************************
+// * 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 org.apache.juneau.internal.StringUtils.*;
+
+import java.lang.reflect.*;
+import java.text.*;
+
+import org.apache.juneau.*;
+
+/**
+ * Exception thrown to trigger an error HTTP status.
+ *
+ * <p>
+ * REST methods on subclasses of {@link RestServlet} can throw this exception
to trigger an HTTP status other than the
+ * automatically-generated <code>404</code>, <code>405</code>, and
<code>500</code> statuses.
+ */
+public class RestException extends FormattedRuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final int status;
+ private int occurrence;
+
+ /**
+ * Constructor.
+ *
+ * @param status The HTTP status code.
+ * @param msg The status message.
+ * @param args Optional {@link MessageFormat}-style arguments.
+ */
+ public RestException(int status, String msg, Object...args) {
+ super(msg, args);
+ this.status = status;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param status The HTTP status code.
+ * @param cause The root exception.
+ */
+ public RestException(int status, Throwable cause) {
+ this(status, cause.getLocalizedMessage());
+ initCause(cause);
+ }
+
+
+ /**
+ * Sets the inner cause for this exception.
+ *
+ * @param cause The inner cause.
+ * @return This object (for method chaining).
+ */
+ @Override /* Throwable */
+ public synchronized RestException initCause(Throwable cause) {
+ super.initCause(cause);
+ return this;
+ }
+
+ /**
+ * Returns the root cause of this exception.
+ *
+ * <p>
+ * The root cause is the first exception in the init-cause parent chain
that's not one of the following:
+ * <ul>
+ * <li>{@link RestException}
+ * <li>{@link InvocationTargetException}
+ * </ul>
+ *
+ * @return The root cause of this exception, or <jk>null</jk> if no
root cause was found.
+ */
+ public Throwable getRootCause() {
+ Throwable t = this;
+ while(t != null) {
+ t = t.getCause();
+ if (! (t instanceof RestException || t instanceof
InvocationTargetException))
+ return t;
+ }
+ return null;
+ }
+
+ /**
+ * Returns all error messages from all errors in this stack.
+ *
+ * <p>
+ * Typically useful if you want to render all the error messages in the
stack, but don't want to render all the
+ * stack traces too.
+ *
+ * @param scrubForXssVulnerabilities
+ * If <jk>true</jk>, replaces <js>'<'</js>, <js>'>'</js>,
and <js>'&'</js> characters with spaces.
+ * @return All error messages from all errors in this stack.
+ */
+ public String getFullStackMessage(boolean scrubForXssVulnerabilities) {
+ String msg = getMessage();
+ StringBuilder sb = new StringBuilder();
+ if (msg != null) {
+ if (scrubForXssVulnerabilities)
+ msg = msg.replace('<', ' ').replace('>', '
').replace('&', ' ');
+ sb.append(msg);
+ }
+ Throwable e = getCause();
+ while (e != null) {
+ msg = e.getMessage();
+ if (msg != null && scrubForXssVulnerabilities)
+ msg = msg.replace('<', ' ').replace('>', '
').replace('&', ' ');
+ String cls = e.getClass().getSimpleName();
+ if (msg == null)
+ sb.append(format("\nCaused by ({0})", cls));
+ else
+ sb.append(format("\nCaused by ({0}): {1}", cls,
msg));
+ e = e.getCause();
+ }
+ return sb.toString();
+ }
+
+ @Override /* Object */
+ public int hashCode() {
+ int i = 0;
+ Throwable t = this;
+ while (t != null) {
+ for (StackTraceElement e : t.getStackTrace())
+ i ^= e.hashCode();
+ t = t.getCause();
+ }
+ return i;
+ }
+
+ void setOccurrence(int occurrence) {
+ this.occurrence = occurrence;
+ }
+
+ /**
+ * Returns the number of times this exception occurred on this servlet.
+ *
+ * <p>
+ * This only gets set if {@link RestContext#REST_useStackTraceHashes}
is enabled on the servlet.
+ *
+ * @return
+ * The occurrence number if {@link
RestContext#REST_useStackTraceHashes} is enabled, or <code>0</code> otherwise.
+ */
+ public int getOccurrence() {
+ return occurrence;
+ }
+
+ /**
+ * Returns the HTTP status code.
+ *
+ * @return The HTTP status code.
+ */
+ public int getStatus() {
+ return status;
+ }
+}
Propchange:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestException.java
------------------------------------------------------------------------------
svn:mime-type = text/plain