You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/09/08 23:25:37 UTC
svn commit: r21540 [13/27] - in /release/incubator/juneau:
juneau-rest-client/ juneau-rest-client/.settings/ juneau-rest-client/bin/
juneau-rest-client/src/ juneau-rest-client/src/main/
juneau-rest-client/src/main/java/ juneau-rest-client/src/main/java...
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