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/03/17 02:17:09 UTC
[4/7] incubator-juneau git commit: Allow @RestResource/@RestMethod
annotations to be used on any classes.
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/07843d64/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
new file mode 100644
index 0000000..243afee
--- /dev/null
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -0,0 +1,1418 @@
+// ***************************************************************************************************************************
+// * 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 java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+import javax.activation.*;
+import javax.servlet.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.encoders.*;
+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.vars.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+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.
+ * <p>
+ * <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.
+ * <p>
+ * <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.
+ * <p>
+ * <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.
+ * <p>
+ * <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.
+ * <p>
+ * <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.
+ * <p>
+ * <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.
+ * <p>
+ * <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 convertable 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";
+
+
+ //--------------------------------------------------------------------------------
+ // Automatically added properties.
+ //--------------------------------------------------------------------------------
+
+ /**
+ * The request servlet path.
+ * <p>
+ * Automatically added to properties returned by {@link SerializerSession#getProperties()} and {@link ParserSession#getProperties()}.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getServletPath()}
+ */
+ public static final String REST_servletPath = "RestServlet.servletPath";
+
+ /**
+ * The request servlet URI.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getServletURI()}
+ */
+ public static final String REST_servletURI = "RestServlet.servletURI";
+
+ /**
+ * The request servlet URI.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getRelativeServletURI()}
+ */
+ public static final String REST_relativeServletURI = "RestServlet.relativeServletURI";
+
+ /**
+ * The request URI path info.
+ * <p>
+ * Automatically added to properties returned by {@link SerializerSession#getProperties()} and {@link ParserSession#getProperties()}.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getPathInfo()}
+ */
+ public static final String REST_pathInfo = "RestServlet.pathInfo";
+
+ /**
+ * The request URI.
+ * <p>
+ * Automatically added to properties returned by {@link SerializerSession#getProperties()} and {@link ParserSession#getProperties()}.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getRequestURI()}
+ */
+ public static final String REST_requestURI = "RestServlet.requestURI";
+
+ /**
+ * The request method.
+ * <p>
+ * Automatically added to properties returned by {@link SerializerSession#getProperties()} and {@link ParserSession#getProperties()}.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getMethod()}
+ */
+ public static final String REST_method = "RestServlet.method";
+
+ /**
+ * The localized servlet title.
+ * <p>
+ * Automatically added to properties returned by {@link SerializerSession#getProperties()} and {@link ParserSession#getProperties()}.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getServletTitle()}
+ */
+ public static final String REST_servletTitle = "RestServlet.servletTitle";
+
+ /**
+ * The localized servlet description.
+ * <p>
+ * Automatically added to properties returned by {@link SerializerSession#getProperties()} and {@link ParserSession#getProperties()}.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getServletDescription()}
+ */
+ public static final String REST_servletDescription = "RestServlet.servletDescription";
+
+ /**
+ * The localized method summary.
+ * <p>
+ * Automatically added to properties returned by {@link SerializerSession#getProperties()} and {@link ParserSession#getProperties()}.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getMethodSummary()}
+ */
+ public static final String REST_methodSummary = "RestServlet.methodSummary";
+
+ /**
+ * The localized method description.
+ * <p>
+ * Automatically added to properties returned by {@link SerializerSession#getProperties()} and {@link ParserSession#getProperties()}.
+ * <p>
+ * Equivalent to the value returned by {@link RestRequest#getMethodDescription()}
+ */
+ public static final String REST_methodDescription = "RestServlet.methodDescription";
+
+ private final Object resource;
+ private final RestConfig config;
+ private final boolean
+ allowHeaderParams,
+ allowBodyParam,
+ renderResponseStackTraces,
+ useStackTraceHashes;
+ private final String
+ defaultCharset,
+ paramFormat,
+ clientVersionHeader,
+ fullPath;
+ private final Set<String> allowMethodParams;
+
+ private final ObjectMap properties;
+ private final Class<?>[]
+ beanFilters,
+ pojoSwaps;
+ 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 styleSheet, 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;
+
+ // 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 Map<String,byte[]> resourceStreams = new ConcurrentHashMap<String,byte[]>();
+ private final Map<String,String> resourceStrings = new ConcurrentHashMap<String,String>();
+ private final ConcurrentHashMap<Integer,AtomicInteger> stackTraceHashes = new ConcurrentHashMap<Integer,AtomicInteger>();
+
+
+ /**
+ * Constructor.
+ *
+ * @param resource The resource class (a class annotated with {@link RestResource @RestResource}).
+ * @param config The servlet configuration object.
+ * @throws Exception If any initialization problems were encountered.
+ */
+ @SuppressWarnings("unchecked")
+ public RestContext(Object resource, RestConfig config) throws Exception {
+ super(null);
+ try {
+ this.resource = resource;
+ this.config = config;
+
+ Builder b = new Builder(resource, config);
+ this.allowHeaderParams = b.allowHeaderParams;
+ this.allowBodyParam = b.allowBodyParam;
+ this.renderResponseStackTraces = b.renderResponseStackTraces;
+ this.useStackTraceHashes = b.useStackTraceHashes;
+ this.allowMethodParams = Collections.unmodifiableSet(b.allowMethodParams);
+ this.defaultCharset = b.defaultCharset;
+ this.paramFormat = b.paramFormat;
+ this.varResolver = b.varResolver;
+ this.configFile = b.configFile;
+ this.properties = b.properties;
+ this.beanFilters = b.beanFilters;
+ this.pojoSwaps = b.pojoSwaps;
+ this.serializers = b.serializers;
+ this.parsers = b.parsers;
+ this.urlEncodingSerializer = b.urlEncodingSerializer;
+ this.urlEncodingParser = b.urlEncodingParser;
+ this.encoders = b.encoders;
+ this.supportedContentTypes = ArrayUtils.toObjectArray(b.supportedContentTypes, MediaType.class);
+ this.supportedAcceptTypes = ArrayUtils.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 = ArrayUtils.toObjectArray(b.responseHandlers, ResponseHandler.class);
+ this.mimetypesFileTypeMap = b.mimetypesFileTypeMap;
+ this.styleSheet = b.styleSheet;
+ 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;
+
+ //----------------------------------------------------------------------------------------------------
+ // 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>();
+ 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);
+ _javaRestMethods.put(method.getName(), sm);
+
+ String httpMethod = sm.getHttpMethod();
+ if (! routers.containsKey(httpMethod))
+ routers.put(httpMethod, new CallRouter.Builder(httpMethod));
+
+ routers.get(httpMethod).add(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);
+ }
+ }
+ }
+ this.callMethods = Collections.unmodifiableMap(_javaRestMethods);
+
+ 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.
+ RestResourceResolver rrr = resolve(RestResourceResolver.class, config.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;
+ r = c;
+ } else {
+ r = o;
+ }
+
+ RestConfig childConfig = null;
+
+ if (o instanceof Class) {
+ Class<?> oc = (Class<?>)o;
+ childConfig = new RestConfig(config.inner, oc, this);
+ r = rrr.resolve(oc, childConfig);
+ } else {
+ r = o;
+ childConfig = new RestConfig(config.inner, o.getClass(), this);
+ }
+
+ if (r instanceof RestServlet) {
+ RestServlet rs = (RestServlet)r;
+ rs.init(childConfig);
+ path = childConfig.path;
+ childResources.put(path, rs.getContext());
+ } else {
+
+ // Call the init(RestConfig) method.
+ java.lang.reflect.Method m2 = ClassUtils.findPublicMethod(r.getClass(), "init", Void.class, RestConfig.class);
+ if (m2 != null)
+ m2.invoke(r, childConfig);
+
+ RestContext rc2 = new RestContext(r, childConfig);
+
+ // Call the init(RestContext) method.
+ m2 = ClassUtils.findPublicMethod(r.getClass(), "init", Void.class, RestContext.class);
+ if (m2 != null)
+ m2.invoke(r, rc2);
+
+ path = childConfig.path;
+ childResources.put(path, rc2);
+ }
+ }
+
+ callHandler = config.callHandler == null ? new RestCallHandler(this) : resolve(RestCallHandler.class, config.callHandler, this);
+ infoProvider = config.infoProvider == null ? new RestInfoProvider(this) : resolve(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;
+ }
+ initException = null;
+ }
+
+ private static class Builder {
+
+ boolean allowHeaderParams, allowBodyParam, renderResponseStackTraces, useStackTraceHashes;
+ VarResolver varResolver;
+ ConfigFile configFile;
+ ObjectMap properties;
+ Class<?>[] beanFilters;
+ Class<?>[] pojoSwaps;
+ SerializerGroup serializers;
+ ParserGroup parsers;
+ UrlEncodingSerializer urlEncodingSerializer;
+ UrlEncodingParser urlEncodingParser;
+ EncoderGroup encoders;
+ String clientVersionHeader = "", defaultCharset, paramFormat;
+ 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 styleSheet, favIcon;
+ Map<String,String> staticFilesMap;
+ String[] staticFilesPrefixes;
+ MessageBundle messageBundle;
+ Set<String> allowMethodParams = new LinkedHashSet<String>();
+ RestLogger logger;
+ String fullPath;
+
+ @SuppressWarnings("unchecked")
+ private Builder(Object resource, RestConfig sc) throws Exception {
+
+ PropertyStore ps = sc.createPropertyStore();
+
+ LinkedHashMap<Class<?>,RestResource> restResourceAnnotationsChildFirst = ReflectionUtils.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, "");
+
+ for (String m : StringUtils.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(LocalizationVar.class, RequestVar.class, SerializedRequestAttrVar.class, ServletInitParamVar.class, UrlEncodeVar.class)
+ .build()
+ ;
+ configFile = sc.configFile.getResolving(this.varResolver);
+ properties = sc.properties;
+ Collections.reverse(sc.beanFilters);
+ Collections.reverse(sc.pojoSwaps);
+ beanFilters = ArrayUtils.toObjectArray(sc.beanFilters, Class.class);
+ pojoSwaps = ArrayUtils.toObjectArray(sc.pojoSwaps, Class.class);
+ 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).build();
+ parsers = sc.parsers.beanFilters(beanFilters).pojoSwaps(pojoSwaps).properties(properties).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();
+
+ for (Object o : sc.converters)
+ converters.add(resolve(RestConverter.class, o));
+
+ for (Object o : sc.guards)
+ guards.add(resolve(RestGuard.class, o));
+
+ for (Object o : sc.responseHandlers)
+ responseHandlers.add(resolve(ResponseHandler.class, o));
+
+ mimetypesFileTypeMap = sc.mimeTypes;
+
+ VarResolver vr = sc.getVarResolverBuilder().build();
+
+ if (sc.styleSheets != null) {
+ List<InputStream> contents = new ArrayList<InputStream>();
+ for (Object o : sc.styleSheets) {
+ if (o instanceof Pair) {
+ Pair<Class<?>,String> p = (Pair<Class<?>,String>)o;
+ for (String path : StringUtils.split(vr.resolve(StringUtils.toString(p.second())), ','))
+ if (path.startsWith("file://"))
+ contents.add(new FileInputStream(path));
+ else
+ contents.add(ReflectionUtils.getResource(p.first(), path));
+ } else {
+ contents.add(IOUtils.toInputStream(o));
+ }
+ }
+ styleSheet = new StreamResource(MediaType.forString("text/css"), contents.toArray());
+ }
+
+ 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 = IOUtils.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(RestLogger.class, sc.logger);
+
+ fullPath = (sc.parentContext == null ? "" : (sc.parentContext.fullPath + '/')) + sc.path;
+ }
+ }
+
+ /**
+ * Returns the variable resolver for this servlet.
+ * <p>
+ * Variable resolvers are used to replace variables in property values.
+ * </p>
+ * <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 is using variables for resolving URL links when rendering HTML:
+ * </p>
+ * <p class='bcode'>
+ * <ja>@RestMethod</ja>(
+ * name=<js>"GET"</js>, path=<js>"/{name}/*"</js>,
+ * properties={
+ * <ja>@Property</ja>(
+ * name=<jsf>HTMLDOC_links</jsf>,
+ * value=<js>"{up:'$R{requestParentURI}', options:'?method=OPTIONS', editLevel:'$R{servletURI}/editLevel?logger=$R{attribute.name}'}"</js>
+ * )
+ * }
+ * )
+ * <jk>public</jk> LoggerEntry getLogger(RestRequest req, <ja>@Path</ja> String name) <jk>throws</jk> Exception {
+ * </p>
+ * <p>
+ * Calls to <code>req.getProperties().getString(<js>"key"</js>)</code> returns strings with variables resolved.
+ *
+ * @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 = RestUtils.decode(RestUtils.trimSlashes(pathInfo));
+ if (p.indexOf("..") != -1)
+ throw new RestException(SC_NOT_FOUND, "Invalid path");
+ for (Map.Entry<String,String> e : staticFilesMap.entrySet()) {
+ String key = RestUtils.trimSlashes(e.getKey());
+ if (p.startsWith(key)) {
+ String remainder = (p.equals(key) ? "" : p.substring(key.length()));
+ if (remainder.isEmpty() || remainder.startsWith("/")) {
+ String p2 = RestUtils.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 {
+ String n = (locale == null || locale.toString().isEmpty() ? name : name + '|' + locale);
+ if (! resourceStreams.containsKey(n)) {
+ InputStream is = ReflectionUtils.getLocalizedResource(resource.getClass(), name, locale);
+ if (is == null && name.indexOf("..") == -1) {
+ for (String n2 : FileUtils.getCandidateFileNames(name, locale)) {
+ File f = new File(n2);
+ if (f.exists() && f.canRead()) {
+ is = new FileInputStream(f);
+ break;
+ }
+ }
+ }
+ if (is != null) {
+ try {
+ resourceStreams.put(n, ByteArrayCache.DEFAULT.cache(is));
+ } finally {
+ is.close();
+ }
+ }
+ }
+ byte[] b = resourceStreams.get(n);
+ return b == null ? null : new ByteArrayInputStream(b);
+ }
+
+ /**
+ * 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 {
+ String n = (locale == null || locale.toString().isEmpty() ? name : name + '|' + locale);
+ if (! resourceStrings.containsKey(n)) {
+ String s = IOUtils.read(getResource(name, locale));
+ if (s == null)
+ throw new IOException("Resource '"+name+"' not found.");
+ resourceStrings.put(n, s);
+ }
+ return resourceStrings.get(n);
+ }
+
+ /**
+ * 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, IOUtils.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;
+ }
+
+ /**
+ * 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 uppercased 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>.
+ */
+ protected 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}
+ */
+ protected 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 {@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>
+ * <p>
+ * <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, uppercased.
+ * @return <jk>true</jk> if this resource allows the specified method to be overridden.
+ */
+ protected boolean allowMethodParam(String m) {
+ return (! StringUtils.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;
+ }
+
+ /**
+ * 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 the stylesheet for use in the HTML views of the resource.
+ * <p>
+ * This is the contents of the page served up under <js>"/styles.css"</jk>.
+ * <p>
+ * The stylesheet is defined via one of the following:
+ * <ul>
+ * <li>{@link RestResource#stylesheet() @RestResource.stylesheet()} annotation.
+ * <li>{@link RestConfig#setStyleSheet(Object...)}/{@link RestConfig#setStyleSheet(Class, String)} methods.
+ * </ul>
+ *
+ * @return The aggregated stylesheet of this resource. Never <jk>null</jk>.
+ */
+ protected StreamResource getStyleSheet() {
+ return styleSheet;
+ }
+
+ /**
+ * 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 StringUtils.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 (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);
+ }
+
+
+ //----------------------------------------------------------------------------------------------------
+ // Utility methods
+ //----------------------------------------------------------------------------------------------------
+
+ /**
+ * Takes in an object of type T or a Class<T> and either casts or constructs a T.
+ */
+ @SuppressWarnings("unchecked")
+ private static <T> T resolve(Class<T> c, Object o, Object...cArgs) throws RestServletException {
+ if (c.isInstance(o))
+ return (T)o;
+ if (! (o instanceof Class))
+ throw new RestServletException("Invalid object type passed to resolve: ''{0}''. Must be an object of type T or a Class<? extend T>.", o.getClass());
+ Constructor<T> n = ClassUtils.findPublicConstructor((Class<T>)o, cArgs);
+ if (n == null)
+ throw new RestServletException("Could not find public constructor for class ''{0}'' that takes in args {1}", c, JsonSerializer.DEFAULT_LAX.toString(ClassUtils.getClasses(cArgs)));
+ try {
+ return n.newInstance(cArgs);
+ } catch (Exception e) {
+ throw new RestServletException("Exception occurred while constructing class ''{0}''", c).initCause(e);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/07843d64/juneau-rest/src/main/java/org/apache/juneau/rest/RestException.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestException.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestException.java
index 77d695a..299883a 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestException.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestException.java
@@ -116,9 +116,9 @@ public class RestException extends RuntimeException {
/**
* Returns the number of times this exception occurred on this servlet.
* <p>
- * This only gets set if {@link RestServletContext#REST_useStackTraceHashes} is enabled on the servlet.
+ * This only gets set if {@link RestContext#REST_useStackTraceHashes} is enabled on the servlet.
*
- * @return The occurrence number if {@link RestServletContext#REST_useStackTraceHashes} is enabled, or <code>0</code> otherwise.
+ * @return The occurrence number if {@link RestContext#REST_useStackTraceHashes} is enabled, or <code>0</code> otherwise.
*/
public int getOccurrence() {
return occurrence;
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/07843d64/juneau-rest/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestInfoProvider.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
new file mode 100644
index 0000000..e43f514
--- /dev/null
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
@@ -0,0 +1,499 @@
+// ***************************************************************************************************************************
+// * 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.dto.swagger.SwaggerBuilder.*;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.dto.swagger.*;
+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.svl.*;
+
+/**
+ * Class that provides documentation and other related information about a REST resource.
+ * <p>
+ * Subclasses can override these methods to tailor how HTTP REST resources are documented.
+ * Subclasses MUST implement a public constructor that takes in a {@link RestContext} object.
+ * <p>
+ * RestInfoProviders are associated with servlets/resources in one of the following ways:
+ * <ul>
+ * <li>The {@link RestResource#infoProvider @RestResource.infoProvider()} annotation.
+ * <li>The {@link RestConfig#setInfoProvider(Class)}/{@link RestConfig#setInfoProvider(RestInfoProvider)} methods.
+ * </ul>
+ */
+@SuppressWarnings("hiding")
+public class RestInfoProvider {
+
+ private final RestContext context;
+ private final String
+ title,
+ description,
+ termsOfService,
+ contact,
+ license,
+ version,
+ tags,
+ externalDocs;
+ private final ConcurrentHashMap<Locale,Swagger> swaggers = new ConcurrentHashMap<Locale,Swagger>();
+
+ /**
+ * Constructor.
+ * @param context The resource context.
+ */
+ public RestInfoProvider(RestContext context) {
+ this.context = context;
+
+ Builder b = new Builder(context);
+ this.title = b.title;
+ this.description = b.description;
+ this.termsOfService = b.termsOfService;
+ this.contact = b.contact;
+ this.license = b.license;
+ this.version = b.version;
+ this.tags = b.tags;
+ this.externalDocs = b.externalDocs;
+ }
+
+ private static class Builder {
+ private String
+ title,
+ description,
+ termsOfService,
+ contact,
+ license,
+ version,
+ tags,
+ externalDocs;
+
+ Builder(RestContext context) {
+
+ LinkedHashMap<Class<?>,RestResource> restResourceAnnotationsParentFirst = ReflectionUtils.findAnnotationsMapParentFirst(RestResource.class, context.getResource().getClass());
+
+ for (RestResource r : restResourceAnnotationsParentFirst.values()) {
+ if (! r.title().isEmpty())
+ title = r.title();
+ if (! r.description().isEmpty())
+ description = r.description();
+ if (! r.termsOfService().isEmpty())
+ termsOfService = r.termsOfService();
+ if (! r.contact().isEmpty())
+ contact = r.contact();
+ if (! r.license().isEmpty())
+ license = r.license();
+ if (! r.version().isEmpty())
+ version = r.version();
+ if (! r.tags().isEmpty())
+ tags = r.tags();
+ if (! r.externalDocs().isEmpty())
+ externalDocs = r.externalDocs();
+ }
+ }
+ }
+
+ /**
+ * Returns the localized swagger for this REST resource.
+ *
+ * @param req The incoming HTTP request.
+ * @return A new Swagger instance.
+ * @throws RestException
+ */
+ protected Swagger getSwagger(RestRequest req) throws RestException {
+ try {
+ // If a file is defined, use that.
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null)
+ return s;
+
+ s = swagger(
+ info(getTitle(req), getVersion(req))
+ .contact(getContact(req))
+ .license(getLicense(req))
+ .description(getDescription(req))
+ .termsOfService(getTermsOfService(req))
+ )
+ .consumes(context.getSupportedAcceptTypes())
+ .produces(context.getSupportedContentTypes())
+ .tags(getTags(req))
+ .externalDocs(getExternalDocs(req));
+
+ for (CallMethod sm : context.getCallMethods().values()) {
+ if (sm.isRequestAllowed(req)) {
+ Operation o = sm.getSwaggerOperation(req);
+ s.path(
+ sm.getPathPattern(),
+ sm.getHttpMethod().toLowerCase(),
+ o
+ );
+ }
+ }
+ return s;
+ } catch (RestException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ /**
+ * Returns the localized Swagger from the file system.
+ * <p>
+ * Looks for a file called <js>"{ServletClass}_{locale}.json"</js> in the same package
+ * as this servlet and returns it as a parsed {@link Swagger} object.
+ * <p>
+ * Returned objects are cached for later quick-lookup.
+ *
+ * @param locale The locale of the swagger.
+ * @return The parsed swagger object, or <jk>null</jk> if the swagger file could not be found.
+ * @throws RestException
+ */
+ protected Swagger getSwaggerFromFile(Locale locale) throws RestException {
+ Swagger s = swaggers.get(locale);
+ if (s == null) {
+ try {
+ s = context.getResource(Swagger.class, MediaType.JSON, getClass().getSimpleName() + ".json", locale);
+ swaggers.putIfAbsent(locale, s == null ? Swagger.NULL : s);
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+ return s == Swagger.NULL ? null : s;
+ }
+
+ /**
+ * Returns the localized summary of the specified java method on this servlet.
+ * <p>
+ * Subclasses can override this method to provide their own summary.
+ * <p>
+ * The default implementation returns the summary from the following locations (whichever matches first):
+ * </p>
+ * <ol>
+ * <li>{@link RestMethod#summary() @RestMethod.summary()} annotation on the method.
+ * <li><ck>[ClassName].[javaMethodName].summary</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>[javaMethodName].summary</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * </ol>
+ *
+ * @param javaMethodName The name of the Java method whose description we're retrieving.
+ * @param req The current request.
+ * @return The localized summary of the method, or a blank string if no summary was found.
+ */
+ public String getMethodSummary(String javaMethodName, RestRequest req) {
+ CallMethod m = context.getCallMethods().get(javaMethodName);
+ if (m != null)
+ return m.getSummary(req);
+ return "";
+ }
+
+ /**
+ * Returns the localized description of the specified java method on this servlet.
+ * <p>
+ * Subclasses can override this method to provide their own description.
+ * <p>
+ * The default implementation returns the description from the following locations (whichever matches first):
+ * </p>
+ * <ol>
+ * <li>{@link RestMethod#description() @RestMethod.description()} annotation on the method.
+ * <li><ck>[ClassName].[javaMethodName].description</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>[javaMethodName].description</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * </ol>
+ *
+ * @param javaMethodName The name of the Java method whose description we're retrieving.
+ * @param req The current request.
+ * @return The localized description of the method, or a blank string if no description was found.
+ */
+ protected String getMethodDescription(String javaMethodName, RestRequest req) {
+ CallMethod m = context.getCallMethods().get(javaMethodName);
+ if (m != null)
+ return m.getDescription(req);
+ return "";
+ }
+
+ /**
+ * Returns the localized title of this REST resource.
+ * <p>
+ * Subclasses can override this method to provide their own title.
+ * <p>
+ * The default implementation returns the description from the following locations (whichever matches first):
+ * <p>
+ * <ol>
+ * <li>{@link RestResource#title() @RestResourcel.title()} annotation on this class, and then any parent classes.
+ * <li><ck>[ClassName].title</ck> property in resource bundle identified by {@link RestResource#messages() @ResourceBundle.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>title</ck> in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>/info/title</ck> entry in swagger file.
+ * </ol>
+ *
+ * @param req The current request.
+ * @return The localized description of this REST resource, or <jk>null</jk> if no resource description was found.
+ */
+ public String getTitle(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ if (this.title != null)
+ return vr.resolve(this.title);
+ String title = context.getMessages().findFirstString(req.getLocale(), "title");
+ if (title != null)
+ return vr.resolve(title);
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null && s.getInfo() != null)
+ return s.getInfo().getTitle();
+ return null;
+ }
+
+ /**
+ * Returns the localized description of this REST resource.
+ * <p>
+ * Subclasses can override this method to provide their own description.
+ * <p>
+ * The default implementation returns the description from the following locations (whichever matches first):
+ * <ol>
+ * <li>{@link RestResource#description() @RestResource.description()} annotation on this class, and then any parent classes.
+ * <li><ck>[ClassName].description</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>description</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>/info/description</ck> entry in swagger file.
+ * </ol>
+ *
+ * @param req The current request.
+ * @return The localized description of this REST resource, or <jk>null</jk> if no resource description was found.
+ */
+ public String getDescription(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ if (this.description != null)
+ return vr.resolve(this.description);
+ String description = context.getMessages().findFirstString(req.getLocale(), "description");
+ if (description != null)
+ return vr.resolve(description);
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null && s.getInfo() != null)
+ return s.getInfo().getDescription();
+ return null;
+ }
+
+ /**
+ * Returns the localized contact information of this REST resource.
+ * <p>
+ * Subclasses can override this method to provide their own contact information.
+ * <p>
+ * The default implementation returns the contact information from the following locations (whichever matches first):
+ * <ol>
+ * <li>{@link RestResource#contact() @RestResource.contact()} annotation on this class, and then any parent classes.
+ * <li><ck>[ClassName].contact</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>contact</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>/info/contact</ck> entry in swagger file.
+ * </ol>
+ *
+ * @param req The current request.
+ * @return The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+ */
+ public Contact getContact(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ try {
+ if (this.contact != null)
+ return jp.parse(vr.resolve(this.contact), Contact.class);
+ String contact = context.getMessages().findFirstString(req.getLocale(), "contact");
+ if (contact != null)
+ return jp.parse(vr.resolve(contact), Contact.class);
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null && s.getInfo() != null)
+ return s.getInfo().getContact();
+ return null;
+ } catch (ParseException e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ /**
+ * Returns the localized license information of this REST resource.
+ * <p>
+ * Subclasses can override this method to provide their own license information.
+ * <p>
+ * The default implementation returns the license information from the following locations (whichever matches first):
+ * <ol>
+ * <li>{@link RestResource#license() @RestResource.license()} annotation on this class, and then any parent classes.
+ * <li><ck>[ClassName].license</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>license</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>/info/license</ck> entry in swagger file.
+ * </ol>
+ *
+ * @param req The current request.
+ * @return The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+ */
+ public License getLicense(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ try {
+ if (this.license != null)
+ return jp.parse(vr.resolve(this.license), License.class);
+ String license = context.getMessages().findFirstString(req.getLocale(), "license");
+ if (license != null)
+ return jp.parse(vr.resolve(license), License.class);
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null && s.getInfo() != null)
+ return s.getInfo().getLicense();
+ return null;
+ } catch (ParseException e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ /**
+ * Returns the terms-of-service information of this REST resource.
+ * <p>
+ * Subclasses can override this method to provide their own terms-of-service information.
+ * <p>
+ * The default implementation returns the terms-of-service information from the following locations (whichever matches first):
+ * <ol>
+ * <li>{@link RestResource#termsOfService() @RestResource.termsOfService()} annotation on this class, and then any parent classes.
+ * <li><ck>[ClassName].termsOfService</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>termsOfService</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>/info/termsOfService</ck> entry in swagger file.
+ * </ol>
+ *
+ * @param req The current request.
+ * @return The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+ */
+ public String getTermsOfService(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ if (this.termsOfService != null)
+ return vr.resolve(this.termsOfService);
+ String termsOfService = context.getMessages().findFirstString(req.getLocale(), "termsOfService");
+ if (termsOfService != null)
+ return vr.resolve(termsOfService);
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null && s.getInfo() != null)
+ return s.getInfo().getTermsOfService();
+ return null;
+ }
+
+ /**
+ * Returns the version information of this REST resource.
+ * <p>
+ * Subclasses can override this method to provide their own version information.
+ * <p>
+ * The default implementation returns the version information from the following locations (whichever matches first):
+ * <ol>
+ * <li>{@link RestResource#version() @RestResource.version()} annotation on this class, and then any parent classes.
+ * <li><ck>[ClassName].version</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>version</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>/info/version</ck> entry in swagger file.
+ * </ol>
+ *
+ * @param req The current request.
+ * @return The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+ */
+ public String getVersion(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ if (this.version != null)
+ return vr.resolve(this.version);
+ String version = context.getMessages().findFirstString(req.getLocale(), "version");
+ if (version != null)
+ return vr.resolve(version);
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null && s.getInfo() != null)
+ return s.getInfo().getVersion();
+ return null;
+ }
+
+ /**
+ * Returns the version information of this REST resource.
+ * <p>
+ * Subclasses can override this method to provide their own version information.
+ * <p>
+ * The default implementation returns the version information from the following locations (whichever matches first):
+ * <ol>
+ * <li>{@link RestResource#version() @RestResource.version()} annotation on this class, and then any parent classes.
+ * <li><ck>[ClassName].version</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>version</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>/info/version</ck> entry in swagger file.
+ * </ol>
+ *
+ * @param req The current request.
+ * @return The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+ */
+ public List<Tag> getTags(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ try {
+ if (this.tags != null)
+ return jp.parse(vr.resolve(this.tags), ArrayList.class, Tag.class);
+ String tags = context.getMessages().findFirstString(req.getLocale(), "tags");
+ if (tags != null)
+ return jp.parse(vr.resolve(tags), ArrayList.class, Tag.class);
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null)
+ return s.getTags();
+ return null;
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ /**
+ * Returns the version information of this REST resource.
+ * <p>
+ * Subclasses can override this method to provide their own version information.
+ * <p>
+ * The default implementation returns the version information from the following locations (whichever matches first):
+ * <ol>
+ * <li>{@link RestResource#version() @RestResource.version()} annotation on this class, and then any parent classes.
+ * <li><ck>[ClassName].version</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>version</ck> property in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+ * annotation for this class, then any parent classes.
+ * <li><ck>/info/version</ck> entry in swagger file.
+ * </ol>
+ *
+ * @param req The current request.
+ * @return The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+ */
+ public ExternalDocumentation getExternalDocs(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ try {
+ if (this.externalDocs != null)
+ return jp.parse(vr.resolve(this.externalDocs), ExternalDocumentation.class);
+ String externalDocs = context.getMessages().findFirstString(req.getLocale(), "externalDocs");
+ if (externalDocs != null)
+ return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null)
+ return s.getExternalDocs();
+ return null;
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+}