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:07 UTC
[2/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/RestServlet.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestServlet.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestServlet.java
index f876360..d7288d3 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestServlet.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestServlet.java
@@ -12,48 +12,17 @@
// ***************************************************************************************************************************
package org.apache.juneau.rest;
-import static java.lang.String.*;
import static java.util.logging.Level.*;
import static javax.servlet.http.HttpServletResponse.*;
-import static org.apache.juneau.dto.swagger.SwaggerBuilder.*;
-import static org.apache.juneau.internal.ArrayUtils.*;
-import static org.apache.juneau.internal.ClassUtils.*;
-import static org.apache.juneau.rest.RestServlet.ParamType.*;
-import static org.apache.juneau.rest.RestServletContext.*;
-import static org.apache.juneau.rest.annotation.Inherit.*;
-import static org.apache.juneau.serializer.SerializerContext.*;
import java.io.*;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.*;
-import java.lang.reflect.Method;
-import java.nio.charset.*;
import java.text.*;
-import java.util.*;
-import java.util.concurrent.*;
import java.util.logging.*;
-import javax.activation.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.juneau.*;
-import org.apache.juneau.dto.swagger.*;
-import org.apache.juneau.encoders.*;
-import org.apache.juneau.encoders.Encoder;
-import org.apache.juneau.ini.*;
-import org.apache.juneau.internal.*;
-import org.apache.juneau.json.*;
-import org.apache.juneau.parser.*;
-import org.apache.juneau.parser.ParseException;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.annotation.Properties;
-import org.apache.juneau.rest.response.*;
-import org.apache.juneau.rest.vars.*;
-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.*;
/**
@@ -61,3265 +30,253 @@ import org.apache.juneau.utils.*;
* <p>
* Refer to <a class="doclink" href="package-summary.html#TOC">REST Servlet API</a> for information about using this class.
*/
-@SuppressWarnings({"rawtypes","hiding"})
+@SuppressWarnings("hiding")
public abstract class RestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
- static final SortedMap<String,Charset> availableCharsets = new TreeMap<String,Charset>(String.CASE_INSENSITIVE_ORDER);
- static {
- availableCharsets.putAll(Charset.availableCharsets());
- }
- // Map of HTTP method names (e.g. GET/PUT/...) to ResourceMethod implementations for it. Populated during resource initialization.
- private final Map<String,ResourceMethod> restMethods = new LinkedHashMap<String,ResourceMethod>();
-
- // The list of all @RestMethod annotated methods in the order they appear in the class.
- private final Map<String,MethodMeta> javaRestMethods = new LinkedHashMap<String,MethodMeta>();
-
- // Child resources of this resource defined through getX() methods on this class.
- private final Map<String,RestServlet> childResources = new LinkedHashMap<String,RestServlet>();
-
- private RestServlet parentResource;
-
- private ServletConfig servletConfig;
- private volatile boolean isInitialized = false;
- private Exception initException; // Exception thrown by init() method (cached so it can be thrown on all subsequent requests).
- private JuneauLogger logger;
- private MessageBundle msgs; // NLS messages.
-
- private Map<Integer,Integer> stackTraceHashes = new HashMap<Integer,Integer>();
- private String path;
-
- private LinkedHashMap<Class<?>,RestResource> restResourceAnnotationsChildFirst, restResourceAnnotationsParentFirst;
-
- private UrlEncodingSerializer urlEncodingSerializer;
- private UrlEncodingParser urlEncodingParser;
- private ObjectMap properties;
- private RestGuard[] guards;
- private Class<?>[] beanFilters, pojoSwaps;
- private RestConverter[] converters;
- private TreeMap<String,String> defaultRequestHeaders;
- private Map<String,Object> defaultResponseHeaders;
- private EncoderGroup encoders;
- private SerializerGroup serializers;
- private ParserGroup parsers;
- private MimetypesFileTypeMap mimetypesFileTypeMap;
- private BeanContext beanContext;
- private VarResolver varResolver;
- private String title, description, termsOfService, contact, license, version, tags, externalDocs;
- private Map<String,byte[]> resourceStreams = new ConcurrentHashMap<String,byte[]>();
- private Map<String,String> resourceStrings = new ConcurrentHashMap<String,String>();
- private ConfigFile configFile, resolvingConfigFile;
- private String configPath;
- private StreamResource styleSheet, favIcon;
- private Map<String,String> staticFilesMap;
- private String[] staticFilesPrefixes;
- private ResponseHandler[] responseHandlers;
- private String clientVersionHeader = "";
- private ConcurrentHashMap<Locale,Swagger> swaggers = new ConcurrentHashMap<Locale,Swagger>();
-
- RestServletContext context;
-
- // In-memory cache of images and stylesheets in the org.apache.juneau.rest.htdocs package.
- private Map<String,StreamResource> staticFilesCache = new ConcurrentHashMap<String,StreamResource>();
-
- // The following code block is executed before the constructor is called to
- // allow the config file to be accessed during object creation.
- // e.g. private String myConfig = getConfig().getString("myConfig");
- {
- VarResolverBuilder vrb = createVarResolver();
-
- // @RestResource annotations from bottom to top.
- restResourceAnnotationsChildFirst = ReflectionUtils.findAnnotationsMap(RestResource.class, getClass());
-
- // @RestResource annotations from top to bottom.
- restResourceAnnotationsParentFirst = CollectionUtils.reverse(restResourceAnnotationsChildFirst);
-
- for (RestResource r : restResourceAnnotationsParentFirst.values()) {
- if (! r.config().isEmpty())
- configPath = r.config();
- }
-
- try {
- configFile = createConfigFile(vrb);
- vrb.contextObject(ConfigFileVar.SESSION_config, configFile);
- } catch (IOException e) {
- this.initException = e;
- }
+ private RestConfig config;
+ private RestContext context;
+ private boolean isInitialized = false;
+ private Exception initException;
- varResolver = vrb.build();
- }
@Override /* Servlet */
- public synchronized void init(ServletConfig servletConfig) throws ServletException {
+ public final synchronized void init(ServletConfig servletConfig) throws ServletException {
try {
- log(FINE, "Servlet {0} init called.", getClass().getName());
- this.servletConfig = servletConfig;
-
- if (isInitialized)
- return;
-
- super.init(servletConfig);
-
- // 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 (msgs == null)
- msgs = new MessageBundle(c, r.messages());
- else
- msgs.addSearchPath(c, r.messages());
- }
- }
- for (RestResource r : restResourceAnnotationsParentFirst.values()) {
- if (! r.title().isEmpty())
- title = r.title();
- if (! r.description().isEmpty())
- description = r.description();
- if (! r.clientVersionHeader().isEmpty())
- clientVersionHeader = r.clientVersionHeader();
- 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();
- }
-
- if (msgs == null)
- msgs = new MessageBundle(this.getClass(), "");
- if (clientVersionHeader.isEmpty())
- clientVersionHeader = "X-Client-Version";
-
- styleSheet = createStyleSheet();
- favIcon = createFavIcon();
- staticFilesMap = Collections.unmodifiableMap(createStaticFilesMap());
- staticFilesPrefixes = staticFilesMap.keySet().toArray(new String[0]);
-
- properties = createProperties();
- beanFilters = createBeanFilters();
- pojoSwaps = createPojoSwaps();
- context = PropertyStore.create().setProperties(properties).getContext(RestServletContext.class);
- beanContext = createBeanContext(properties, beanFilters, pojoSwaps);
- urlEncodingSerializer = createUrlEncodingSerializer(properties, beanFilters, pojoSwaps).build();
- urlEncodingParser = createUrlEncodingParser(properties, beanFilters, pojoSwaps).build();
- serializers = createSerializers(properties, beanFilters, pojoSwaps).build();
- parsers = createParsers(properties, beanFilters, pojoSwaps).build();
- converters = createConverters(properties);
- encoders = createEncoders(properties).build();
- guards = createGuards(properties);
- mimetypesFileTypeMap = createMimetypesFileTypeMap(properties);
- defaultRequestHeaders = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
- defaultRequestHeaders.putAll(createDefaultRequestHeaders(properties));
- defaultResponseHeaders = createDefaultResponseHeaders(properties);
- responseHandlers = createResponseHandlers(properties);
-
- // Discover the @RestMethod methods available on the resource.
- List<String> methodsFound = new LinkedList<String>(); // Temporary to help debug transient duplicate method issue.
- for (java.lang.reflect.Method method : this.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());
-
- MethodMeta sm = new MethodMeta(method);
- javaRestMethods.put(method.getName(), sm);
- ResourceMethod rm = restMethods.get(sm.httpMethod);
- if (rm == null)
- restMethods.put(sm.httpMethod, sm);
- else if (rm instanceof MultiMethod)
- ((MultiMethod)rm).addSimpleMethod(sm);
- else
- restMethods.put(sm.httpMethod, new MultiMethod((MethodMeta)rm, 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);
- }
- }
+ RestConfig rsc = new RestConfig(servletConfig, this.getClass(), null);
+ init(rsc);
+ if (! isInitialized) {
+ // Subclass may not have called super.init(RestServletConfig), so initialize here.
+ createContext(rsc);
+ super.init(servletConfig);
}
-
- for (ResourceMethod m : restMethods.values())
- m.complete();
-
- // Discover the child resources.
- childResources.putAll(createChildrenMap());
-
- for (RestServlet child : childResources.values())
- child.init(servletConfig);
-
- varResolver = varResolver
- .builder()
- .vars(LocalizationVar.class, RequestVar.class, SerializedRequestAttrVar.class, ServletInitParamVar.class, UrlEncodeVar.class)
- .build()
- ;
-
} catch (RestException e) {
// Thrown RestExceptions are simply caught and rethrown on subsequent calls to service().
initException = e;
log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
- title = String.valueOf(initException.getLocalizedMessage());
} catch (ServletException e) {
initException = e;
log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
- title = String.valueOf(initException.getLocalizedMessage());
throw e;
} catch (Exception e) {
initException = e;
log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
- title = String.valueOf(initException.getLocalizedMessage());
throw new ServletException(e);
} catch (Throwable e) {
initException = new Exception(e);
log(SEVERE, e, "Servlet init error on class ''{0}''", getClass().getName());
- title = String.valueOf(initException.getLocalizedMessage());
throw new ServletException(e);
} finally {
isInitialized = true;
}
}
-
- //--------------------------------------------------------------------------------
- // Initialization methods
- //--------------------------------------------------------------------------------
-
- /**
- * Creates the child resources of this resource.
- * <p>
- * Default implementation calls {@link #createChildren()} and uses the {@link RestResource#path() @RestResource.path()} annotation
- * on each child to identify the subpath for the resource which become the keys in this map.
- * It then calls the {@link #setParent(RestServlet)} method on the child resource.
- * <p>
- * Subclasses can override this method to programatically create child resources
- * without using the {@link RestResource#children() @RestResource.children()} annotation.
- * When overridding this method, you are responsible for calling {@link #setParent(RestServlet)} on the
- * child resources.
- *
- * @return The new mutable list of child resource instances.
- * @throws Exception If an error occurred during servlet instantiation.
- */
- protected Map<String,RestServlet> createChildrenMap() throws Exception {
- Map<String,RestServlet> m = new LinkedHashMap<String,RestServlet>();
- for (RestServlet r : createChildren()) {
- r.setParent(this);
- String p = r.findPath();
- if (p == null)
- throw new RestServletException("Child resource ''{0}'' does not define a ''@RestResource.path'' attribute.", r.getClass().getName());
- m.put(p, r);
- }
- return m;
- }
-
- /**
- * Creates instances of child resources for this servlet.
- * <p>
- * Default implementation uses the {@link RestResource#children() @RestResource.children()} annotation to identify and
- * instantiate children.
- * <p>
- * Subclasses can override this method to programatically create child resources
- * without using the {@link RestResource#children() @RestResource.children()} annotation.
- *
- * @return The new mutable list of child resource instances.
- * @throws Exception If an error occurred during servlet instantiation.
- */
- protected List<RestServlet> createChildren() throws Exception {
- List<RestServlet> l = new LinkedList<RestServlet>();
- for (Class<?> c : getChildClasses()) {
- if (isParentClass(RestServlet.class, c))
- l.add((RestServlet)c.newInstance());
- else
- l.add(resolveChild(c));
- }
- return l;
- }
-
- /**
- * Programmatic equivalent to the {@link RestResource#children() @RestResource.children()} annotation.
- * <p>
- * Subclasses can override this method to provide customized list of child resources.
- * (e.g. different children based on values specified in the config file).
- * <p>
- * Default implementation simply returns the value from the {@link RestResource#children() @RestResource.children()} annotation.
- *
- * @return The new mutable list of child resource instances.
- * @throws Exception If an error occurred during servlet instantiation.
- */
- protected Class<?>[] getChildClasses() throws Exception {
- List<Class<?>> l = new ArrayList<Class<?>>();
- List<RestResource> rr = ReflectionUtils.findAnnotations(RestResource.class, getClass());
- for (RestResource r : rr)
- l.addAll(Arrays.asList(r.children()));
- return l.toArray(new Class<?>[l.size()]);
- }
-
- /**
- * Creates the class-level properties associated with this servlet.
- * <p>
- * Subclasses can override this method to provide their own class-level properties for this servlet, typically
- * by calling <code><jk>super</jk>.createProperties()</code> and appending to the map.
- * However, in most cases, the existing set of properties can be added to by overridding {@link #getProperties()}
- * and appending to the map returned by <code><jk>super</jk>.getProperties()</code>
- * <p>
- * By default, the map returned by this method contains the following:
- * </p>
- * <ul class='spaced-list'>
- * <li>Servlet-init parameters.
- * <li>{@link RestResource#properties()} annotations in parent-to-child order.
- * <li>{@link SerializerContext#SERIALIZER_relativeUriBase} from {@link ServletConfig#getServletContext()}.
- * </ul>
- *
- * @return The resource properties as an {@link ObjectMap}.
- */
- protected ObjectMap createProperties() {
- ObjectMap m = new ObjectMap();
-
- ServletContext ctx = servletConfig.getServletContext();
-
- // Workaround for bug in Jetty that causes context path to always end in "null".
- String ctxPath = ctx.getContextPath();
- if (ctxPath.endsWith("null"))
- ctxPath = ctxPath.substring(0, ctxPath.length()-4);
- m.put(SERIALIZER_relativeUriBase, ctxPath);
-
- // Get the initialization parameters.
- for (Enumeration ep = servletConfig.getInitParameterNames(); ep.hasMoreElements();) {
- String p = (String)ep.nextElement();
- String initParam = servletConfig.getInitParameter(p);
- m.put(p, initParam);
- }
-
- // Properties are loaded in parent-to-child order to allow overrides.
- for (RestResource r : restResourceAnnotationsParentFirst.values())
- for (Property p : r.properties())
- m.append(getVarResolver().resolve(p.name()), getVarResolver().resolve(p.value()));
-
- return m;
- }
-
- /**
- * Creates the class-level bean filters associated with this servlet.
- * <p>
- * Subclasses can override this method to provide their own class-level bean filters for this servlet.
- * <p>
- * By default, returns the bean filters specified through the {@link RestResource#beanFilters() @RestResource.beanFilters()} annotation in child-to-parent order.
- * (i.e. bean filters will be applied in child-to-parent order with child annotations overriding parent annotations when
- * the same filters are applied).
- *
- * @return The new set of transforms associated with this servet.
- */
- protected Class<?>[] createBeanFilters() {
- List<Class<?>> l = new LinkedList<Class<?>>();
-
- // Bean filters are loaded in parent-to-child order to allow overrides.
- for (RestResource r : restResourceAnnotationsChildFirst.values())
- for (Class c : r.beanFilters())
- l.add(c);
-
- return l.toArray(new Class<?>[l.size()]);
- }
-
- /**
- * Creates the class-level POJO swaps associated with this servlet.
- * <p>
- * Subclasses can override this method to provide their own class-level POJO swaps for this servlet.
- * <p>
- * By default, returns the transforms specified through the {@link RestResource#pojoSwaps() @RestResource.pojoSwaps()} annotation in child-to-parent order.
- * (i.e. POJO swaps will be applied in child-to-parent order with child annotations overriding parent annotations when
- * the same swaps are applied).
- *
- * @return The new set of transforms associated with this servet.
- */
- protected Class<?>[] createPojoSwaps() {
- List<Class<?>> l = new LinkedList<Class<?>>();
-
- // Swaps are loaded in parent-to-child order to allow overrides.
- for (RestResource r : restResourceAnnotationsChildFirst.values())
- for (Class c : r.pojoSwaps())
- l.add(c);
-
- return l.toArray(new Class<?>[l.size()]);
- }
-
- /**
- * Creates the {@link BeanContext} object used for parsing path variables and header values.
- * <p>
- * Subclasses can override this method to provide their own specialized bean context.
- *
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @param beanFilters Servlet-level bean filters returned by {@link #createBeanFilters()}.
- * @param pojoSwaps Servlet-level POJO swaps returned by {@link #createPojoSwaps()}.
- * @return The new bean context.
- * @throws Exception If bean context not be constructed for any reason.
- */
- protected BeanContext createBeanContext(ObjectMap properties, Class<?>[] beanFilters, Class<?>[] pojoSwaps) throws Exception {
- return PropertyStore.create().addBeanFilters(beanFilters).addPojoSwaps(pojoSwaps).setProperties(properties).getBeanContext();
- }
-
- /**
- * Creates the URL-encoding serializer used for serializing object passed to {@link Redirect}.
- * <p>
- * Subclasses can override this method to provide their own specialized serializer.
- *
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @param beanFilters Servlet-level bean filters returned by {@link #createBeanFilters()}.
- * @param pojoSwaps Servlet-level POJO swaps returned by {@link #createPojoSwaps()}.
- * @return The new URL-Encoding serializer.
- * @throws Exception If the serializer could not be constructed for any reason.
- */
- protected UrlEncodingSerializerBuilder createUrlEncodingSerializer(ObjectMap properties, Class<?>[] beanFilters, Class<?>[] pojoSwaps) throws Exception {
- return new UrlEncodingSerializerBuilder().properties(properties).beanFilters(beanFilters).pojoSwaps(pojoSwaps);
- }
-
- /**
- * Creates the URL-encoding parser used for parsing URL query parameters.
- * <p>
- * Subclasses can override this method to provide their own specialized parser.
- *
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @param beanFilters Servlet-level bean filters returned by {@link #createBeanFilters()}.
- * @param pojoSwaps Servlet-level POJO swaps returned by {@link #createPojoSwaps()}.
- * @return The new URL-Encoding parser.
- * @throws Exception If the parser could not be constructed for any reason.
- */
- protected UrlEncodingParserBuilder createUrlEncodingParser(ObjectMap properties, Class<?>[] beanFilters, Class<?>[] pojoSwaps) throws Exception {
- return new UrlEncodingParserBuilder().properties(properties).beanFilters(beanFilters).pojoSwaps(pojoSwaps);
- }
-
/**
- * Creates the serializer group containing serializers used for serializing output POJOs in HTTP responses.
+ * Resource initialization method.
* <p>
- * Subclasses can override this method to provide their own set of serializers for this servlet.
- * They can do this by either creating a new {@link SerializerGroup} from scratch, or appending to the
- * group returned by <code><jk>super</jk>.createSerializers()</code>.
+ * Identical to {@link Servlet#init(ServletConfig)} except the config object provides
+ * access to the external config file, configuration properties, and variable resolver
+ * defined for this resource.
* <p>
- * By default, returns the serializers defined through {@link RestResource#serializers() @RestResource.serializers()} on this class
- * and all parent classes.
- *
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @param beanFilters Servlet-level bean filters returned by {@link #createBeanFilters()}.
- * @param pojoSwaps Servlet-level POJO swaps returned by {@link #createPojoSwaps()}.
- * @return The group of serializers.
- * @throws Exception If serializer group could not be constructed for any reason.
- */
- protected SerializerGroupBuilder createSerializers(ObjectMap properties, Class<?>[] beanFilters, Class<?>[] pojoSwaps) throws Exception {
- SerializerGroupBuilder g = new SerializerGroupBuilder();
-
- // Serializers are loaded in parent-to-child order to allow overrides.
- for (RestResource r : restResourceAnnotationsParentFirst.values())
- g.append(reverse(r.serializers()));
-
- return g.properties(properties).beanFilters(beanFilters).pojoSwaps(pojoSwaps);
- }
-
- /**
- * Creates the parser group containing parsers used for parsing input into POJOs from HTTP requests.
+ * Classes can also use {@link HttpServlet#init()} and {@link RestServlet#getServletConfig()}
+ * as well to perform initialization.
* <p>
- * Subclasses can override this method to provide their own set of parsers for this servlet.
- * They can do this by either creating a new {@link ParserGroup} from scratch, or appending to the
- * group returned by <code><jk>super</jk>.createParsers()</code>.
+ * Note that if you override this method, you must first call <code><jk>super</jk>.init(servletConfig)</code>!
* <p>
- * By default, returns the parsers defined through {@link RestResource#parsers() @RestResource.parsers()} on this class
- * and all parent classes.
+ * Resource classes that don't extend from {@link RestServlet} can add this method to their class
+ * to get access to the config object.
*
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @param beanFilters Servlet-level bean filters returned by {@link #createBeanFilters()}.
- * @param pojoSwaps Servlet-level POJO swaps returned by {@link #createPojoSwaps()}.
- * @return The group of parsers.
- * @throws Exception If parser group could not be constructed for any reason.
+ * @param config The servlet configuration.
+ * @throws Exception Any exception can be thrown to signal an initialization failure.
*/
- protected ParserGroupBuilder createParsers(ObjectMap properties, Class<?>[] beanFilters, Class<?>[] pojoSwaps) throws Exception {
- ParserGroupBuilder g = new ParserGroupBuilder();
-
- // Parsers are loaded in parent-to-child order to allow overrides.
- for (RestResource r : restResourceAnnotationsParentFirst.values())
- g.append(reverse(r.parsers()));
-
- return g.properties(properties).beanFilters(beanFilters).pojoSwaps(pojoSwaps);
+ public synchronized void init(RestConfig config) throws Exception {
+ if (isInitialized)
+ return;
+ createContext(config);
+ super.init(config);
+ init(context);
}
/**
- * Creates the class-level converters associated with this servlet.
+ * Convenience method if you want to perform initialization on your resource after all configuration settings
+ * have been made.
* <p>
- * Subclasses can override this method to provide their own class-level converters for this servlet.
+ * This allows you to get access to the {@link RestContext} object during initialization.
* <p>
- * By default, returns the converters specified through the {@link RestResource#converters() @RestResource.converters()} annotation in child-to-parent order.
- * (e.g. converters on children will be called before converters on parents).
+ * The default implementation does nothing.
*
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @return The new set of transforms associated with this servet.
- * @throws RestServletException
+ * @param context The servlet context containing all the set-in-stone configurations for this resource.
+ * @throws Exception Any exception can be thrown to signal an initialization failure.
*/
- protected RestConverter[] createConverters(ObjectMap properties) throws RestServletException {
- List<RestConverter> l = new LinkedList<RestConverter>();
+ public synchronized void init(RestContext context) throws Exception {}
- // Converters are loaded in child-to-parent order.
- for (RestResource r : restResourceAnnotationsChildFirst.values())
- for (Class<? extends RestConverter> c : r.converters())
- try {
- l.add(c.newInstance());
- } catch (Exception e) {
- throw new RestServletException("Exception occurred while trying to instantiate RestConverter ''{0}''", c.getSimpleName()).initCause(e);
- }
- return l.toArray(new RestConverter[l.size()]);
+ private synchronized void createContext(RestConfig config) throws Exception {
+ if (isInitialized)
+ return;
+ this.config = config;
+ this.context = new RestContext(this, config);
+ this.isInitialized = true;
}
- /**
- * Creates the {@link EncoderGroup} for this servlet for handling various encoding schemes.
- * <p>
- * Subclasses can override this method to provide their own encoder group, typically by
- * appending to the group returned by <code><jk>super</jk>.createEncoders()</code>.
- * <p>
- * By default, returns a group containing {@link IdentityEncoder#INSTANCE} and all encoders
- * specified through {@link RestResource#encoders() @RestResource.encoders()} annotations in parent-to-child order.
- *
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @return The new encoder group associated with this servet.
- * @throws RestServletException
- */
- protected EncoderGroupBuilder createEncoders(ObjectMap properties) throws RestServletException {
- EncoderGroupBuilder g = new EncoderGroupBuilder().append(IdentityEncoder.INSTANCE);
-
- // Encoders are loaded in parent-to-child order to allow overrides.
- for (RestResource r : restResourceAnnotationsParentFirst.values())
- for (Class<? extends Encoder> c : reverse(r.encoders()))
- try {
- g.append(c);
- } catch (Exception e) {
- throw new RestServletException("Exception occurred while trying to instantiate Encoder ''{0}''", c.getSimpleName()).initCause(e);
- }
- return g;
- }
+ //--------------------------------------------------------------------------------
+ // Other methods
+ //--------------------------------------------------------------------------------
/**
- * Creates the class-level guards associated with this servlet.
- * <p>
- * Subclasses can override this method to provide their own class-level guards for this servlet.
+ * The main service method.
* <p>
- * By default, returns the guards specified through the {@link RestResource#guards() @RestResource.guards()} annotation in child-to-parent order.
- * (i.e. guards on children will be called before guards on parents).
- *
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @return The new set of guards associated with this servet.
- * @throws RestServletException
+ * Subclasses can optionally override this method if they want to tailor the behavior of requests.
*/
- protected RestGuard[] createGuards(ObjectMap properties) throws RestServletException {
- List<RestGuard> l = new LinkedList<RestGuard>();
+ @Override /* Servlet */
+ public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, IOException {
+ try {
- // Guards are loaded in child-to-parent order.
- for (RestResource r : restResourceAnnotationsChildFirst.values())
- for (Class<? extends RestGuard> c : reverse(r.guards()))
- try {
- l.add(c.newInstance());
- } catch (Exception e) {
- throw new RestServletException("Exception occurred while trying to instantiate RestGuard ''{0}''", c.getSimpleName()).initCause(e);
- }
+ if (initException != null) {
+ if (initException instanceof RestException)
+ throw (RestException)initException;
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, initException);
+ }
+ if (context == null)
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, "Servlet not initialized. init(RestServletConfig) was not called.");
+ if (! isInitialized)
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, "Servlet has not been initialized");
- return l.toArray(new RestGuard[l.size()]);
- }
+ context.getCallHandler().service(r1, r2);
- /**
- * Creates an instance of {@link MimetypesFileTypeMap} that is used to determine
- * the media types of static files.
- * <p>
- * Subclasses can override this method to provide their own mappings, or augment the existing
- * map by appending to <code><jk>super</jk>.createMimetypesFileTypeMap()</code>
- *
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @return A new reusable MIME-types map.
- */
- protected MimetypesFileTypeMap createMimetypesFileTypeMap(ObjectMap properties) {
- MimetypesFileTypeMap m = new MimetypesFileTypeMap();
- m.addMimeTypes("text/css css CSS");
- m.addMimeTypes("text/html html htm HTML");
- m.addMimeTypes("text/plain txt text TXT");
- m.addMimeTypes("application/javascript js");
- m.addMimeTypes("image/png png");
- m.addMimeTypes("image/gif gif");
- m.addMimeTypes("application/xml xml XML");
- m.addMimeTypes("application/json json JSON");
- return m;
+ } catch (RestException e) {
+ context.getCallHandler().handleError(r1, r2, e);
+ } catch (Throwable e) {
+ context.getCallHandler().handleError(r1, r2, new RestException(SC_INTERNAL_SERVER_ERROR, e));
+ }
}
/**
- * Creates the set of default request headers for this servlet.
+ * Returns the read-only context object that contains all the configuration information about this resource.
* <p>
- * Default request headers are default values for when HTTP requests do not specify a header value.
- * For example, you can specify a default value for <code>Accept</code> if a request does not specify that header value.
+ * This object is <jk>null</jk> during the call to {@link #init(RestConfig)} but is populated
+ * by the time {@link #init()} is called.
* <p>
- * Subclasses can override this method to provide their own class-level default request headers for this servlet.
- * <p>
- * By default, returns the default request headers specified through the {@link RestResource#defaultRequestHeaders() @RestResource.defaultRequestHeaders()}
- * annotation in parent-to-child order.
- * (e.g. headers defined on children will override the same headers defined on parents).
+ * Resource classes that don't extend from {@link RestServlet} can add the following method to their
+ * class to get access to this context object:
+ * <p class='bcode'>
+ * <jk>public void</jk> init(RestServletContext context) <jk>throws</jk> Exception;
+ * </p>
*
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @return The new set of default request headers associated with this servet.
- * @throws RestServletException
+ * @return The context information on this servlet.
*/
- protected Map<String,String> createDefaultRequestHeaders(ObjectMap properties) throws RestServletException {
- Map<String,String> m = new HashMap<String,String>();
-
- // Headers are loaded in parent-to-child order to allow overrides.
- for (RestResource r : restResourceAnnotationsParentFirst.values()) {
- for (String s : r.defaultRequestHeaders()) {
- String[] h = parseHeader(s);
- if (h == null)
- throw new RestServletException("Invalid default request header specified: ''{0}''. Must be in the format: ''Header-Name: header-value''", s);
- m.put(h[0], h[1]);
- }
- }
-
- return m;
+ protected RestContext getContext() {
+ return context;
}
/**
- * Creates the set of default response headers for this servlet.
+ * Callback method for listening for successful completion of requests.
* <p>
- * Default response headers are headers that will be appended to all responses if those headers have not already been
- * set on the response object.
+ * Subclasses can override this method for gathering performance statistics.
* <p>
- * Subclasses can override this method to provide their own class-level default response headers for this servlet.
+ * The default implementation does nothing.
* <p>
- * By default, returns the default response headers specified through the {@link RestResource#defaultResponseHeaders() @RestResource.defaultResponseHeaders()}
- * annotation in parent-to-child order.
- * (e.g. headers defined on children will override the same headers defined on parents).
+ * Resources that don't extend from {@link RestServlet} can implement an equivalent method by
+ * overriding the {@link RestCallHandler#onSuccess(RestRequest, RestResponse, long)} method.
*
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @return The new set of default response headers associated with this servet.
- * @throws RestServletException
+ * @param req The HTTP request.
+ * @param res The HTTP response.
+ * @param time The time in milliseconds it took to process the request.
*/
- protected Map<String,Object> createDefaultResponseHeaders(ObjectMap properties) throws RestServletException {
- Map<String,Object> m = new LinkedHashMap<String,Object>();
-
- // Headers are loaded in parent-to-child order to allow overrides.
- for (RestResource r : restResourceAnnotationsParentFirst.values()) {
- for (String s : r.defaultResponseHeaders()) {
- String[] h = parseHeader(s);
- if (h == null)
- throw new RestServletException("Invalid default response header specified: ''{0}''. Must be in the format: ''Header-Name: header-value''", s);
- m.put(h[0], h[1]);
- }
- }
-
- return m;
- }
+ protected void onSuccess(RestRequest req, RestResponse res, long time) {}
/**
- * Creates the class-level response handlers associated with this servlet.
+ * Callback method that gets invoked right before the REST Java method is invoked.
* <p>
- * Subclasses can override this method to provide their own class-level response handlers for this servlet.
+ * Subclasses can override this method to override request headers or set request-duration properties
+ * before the Java method is invoked.
* <p>
- * By default, returns the handlers specified through the {@link RestResource#responseHandlers() @RestResource.responseHandlers()}
- * annotation in parent-to-child order.
- * (e.g. handlers on children will be called before handlers on parents).
+ * Resources that don't extend from {@link RestServlet} can implement an equivalent method by
+ * overriding the {@link RestCallHandler#onPreCall(RestRequest)} method.
*
- * @param properties Servlet-level properties returned by {@link #createProperties()}.
- * @return The new set of response handlers associated with this servet.
- * @throws RestException
+ * @param req The HTTP servlet request object.
+ * @throws RestException If any error occurs.
*/
- protected ResponseHandler[] createResponseHandlers(ObjectMap properties) throws RestException {
- List<ResponseHandler> l = new LinkedList<ResponseHandler>();
-
- // Loaded in parent-to-child order to allow overrides.
- for (RestResource r : restResourceAnnotationsParentFirst.values())
- for (Class<? extends ResponseHandler> c : r.responseHandlers())
- try {
- l.add(c.newInstance());
- } catch (Exception e) {
- throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
- }
-
- // Add the default handlers.
- l.add(new StreamableHandler());
- l.add(new WritableHandler());
- l.add(new ReaderHandler());
- l.add(new InputStreamHandler());
- l.add(new RedirectHandler());
- l.add(new DefaultHandler());
-
- return l.toArray(new ResponseHandler[l.size()]);
- }
-
-
- //--------------------------------------------------------------------------------
- // Other methods
- //--------------------------------------------------------------------------------
+ protected void onPreCall(RestRequest req) throws RestException {}
/**
- * Returns the localized Swagger from the file system.
+ * Callback method that gets invoked right after the REST Java method is invoked, but before
+ * the serializer is invoked.
* <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.
+ * Subclasses can override this method to override request and response headers, or
+ * set/override properties used by the serializer.
* <p>
- * Returned objects are cached for later quick-lookup.
+ * Resources that don't extend from {@link RestServlet} can implement an equivalent method by
+ * overriding the {@link RestCallHandler#onPostCall(RestRequest,RestResponse)} method.
*
- * @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 = 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 swagger for this REST resource.
- *
- * @param req The incoming HTTP request.
- * @return A new Swagger instance.
- * @throws RestException
+ * @param req The HTTP servlet request object.
+ * @param res The HTTP servlet response object.
+ * @throws RestException If any error occurs.
*/
- 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(getSupportedAcceptTypes())
- .produces(getSupportedContentTypes())
- .tags(getTags(req))
- .externalDocs(getExternalDocs(req));
-
- for (MethodMeta sm : javaRestMethods.values()) {
- if (sm.isRequestAllowed(req)) {
- Operation o = sm.getSwaggerOperation(req);
- s.path(
- sm.pathPattern.getPatternString(),
- sm.httpMethod.toLowerCase(),
- o
- );
- }
- }
- return s;
- } catch (RestException e) {
- throw e;
- } catch (Exception e) {
- throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
- }
- }
+ protected void onPostCall(RestRequest req, RestResponse res) throws RestException {}
/**
- * Sets the parent of this resource.
+ * Convenience method for calling <code>getContext().getLogger().log(level, msg, args);</code>
*
- * @param parent The parent of this resource.
+ * @param level The log level.
+ * @param msg The message to log.
+ * @param args Optional {@link MessageFormat}-style arguments.
*/
- protected void setParent(RestServlet parent) {
- this.parentResource = parent;
+ protected void log(Level level, String msg, Object...args) {
+ if (context != null)
+ context.getLogger().log(level, msg, args);
}
/**
- * Returns the parent of this resource.
+ * Convenience method for calling <code>getContext().getLogger().log(level, cause, msg, args);</code>
*
- * @return The parent of this resource, or <jk>null</jk> if resource has no parent.
+ * @param level The log level.
+ * @param cause The cause.
+ * @param msg The message to log.
+ * @param args Optional {@link MessageFormat}-style arguments.
*/
- public RestServlet getParent() {
- return this.parentResource;
- }
-
- private static String[] parseHeader(String s) {
- int i = s.indexOf(':');
- if (i == -1)
- return null;
- String name = s.substring(0, i).trim().toLowerCase(Locale.ENGLISH);
- String val = s.substring(i+1).trim();
- return new String[]{name,val};
+ protected void log(Level level, Throwable cause, String msg, Object...args) {
+ if (context != null)
+ context.getLogger().log(level, cause, msg, args);
}
- /**
- * Creates a {@link RestRequest} object based on the specified incoming {@link HttpServletRequest} object.
- * <p>
- * Subclasses may choose to override this method to provide a specialized request object.
- *
- * @param req The request object from the {@link #service(HttpServletRequest, HttpServletResponse)} method.
- * @return The wrapped request object.
- * @throws ServletException If any errors occur trying to interpret the request.
- */
- protected RestRequest createRequest(HttpServletRequest req) throws ServletException {
- return new RestRequest(this, req);
+ @Override /* GenericServlet */
+ public RestConfig getServletConfig() {
+ return config;
}
- /**
- * Creates a {@link RestResponse} object based on the specified incoming {@link HttpServletResponse} object
- * and the request returned by {@link #createRequest(HttpServletRequest)}.
- * <p>
- * Subclasses may choose to override this method to provide a specialized response object.
- *
- * @param req The request object returned by {@link #createRequest(HttpServletRequest)}.
- * @param res The response object from the {@link #service(HttpServletRequest, HttpServletResponse)} method.
- * @return The wrapped response object.
- * @throws ServletException If any erros occur trying to interpret the request or response.
- */
- protected RestResponse createResponse(RestRequest req, HttpServletResponse res) throws ServletException {
- return new RestResponse(this, req, res);
+ @Override /* GenericServlet */
+ public void destroy() {
+ context.destroy();
+ super.destroy();
}
/**
- * Returns whether this resource class can provide an OPTIONS page.
- * <p>
- * By default, returns <jk>false</jk>.
- * <p>
- * Subclasses can override this method to cause the <code>options</code> link to show up in the HTML serialized output.
- *
- * @return <jk>true</jk> if this resource has implemented a {@code getOptions()} method.
+ * Convenience method for calling <code>getContext().getMessages();</code>
+ * @return The resource bundle for this resource. Never <jk>null</jk>.
+ * @see RestContext#getProperties()
*/
- public boolean hasOptionsPage() {
- return false;
+ public MessageBundle getMessages() {
+ return context.getMessages();
}
/**
- * Specify a class-level property.
- * <p>
- * Typically, properties in {@link RestServletContext} can be set in the {@link Servlet#init(ServletConfig)} method.
- *
- * @param key The property name.
- * @param value The property value.
- * @return This object (for method chaining).
+ * Convenience method for calling <code>getContext().getProperties();</code>
+ * @return The resource properties as an {@link ObjectMap}.
+ * @see RestContext#getProperties()
*/
- public synchronized RestServlet setProperty(String key, Object value) {
- getProperties().put(key, value);
- return this;
+ public ObjectMap getProperties() {
+ return getContext().getProperties();
}
/**
- * The main service method.
- * <p>
- * Subclasses can optionally override this method if they want to tailor the behavior of requests.
- */
- @Override /* Servlet */
- public void service(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, IOException {
-
- log(FINE, "HTTP: {0} {1}", r1.getMethod(), r1.getRequestURI());
- long startTime = System.currentTimeMillis();
-
- try {
-
- if (initException != null) {
- if (initException instanceof RestException)
- throw (RestException)initException;
- throw new RestException(SC_INTERNAL_SERVER_ERROR, initException);
- }
-
- if (! isInitialized)
- throw new RestException(SC_INTERNAL_SERVER_ERROR, "Servlet has not been initialized");
-
- String pathInfo = RestUtils.getPathInfoUndecoded(r1); // Can't use r1.getPathInfo() because we don't want '%2F' resolved.
-
- // If this resource has child resources, try to recursively call them.
- if (pathInfo != null && (! childResources.isEmpty()) && (! pathInfo.equals("/"))) {
- int i = pathInfo.indexOf('/', 1);
- String pathInfoPart = i == -1 ? pathInfo.substring(1) : pathInfo.substring(1, i);
- RestServlet childResource = childResources.get(pathInfoPart);
- if (childResource != null) {
- final String pathInfoRemainder = (i == -1 ? null : pathInfo.substring(i));
- final String servletPath = r1.getServletPath() + "/" + pathInfoPart;
- final HttpServletRequest childRequest = new HttpServletRequestWrapper(r1) {
- @Override /* ServletRequest */
- public String getPathInfo() {
- return RestUtils.decode(pathInfoRemainder);
- }
- @Override /* ServletRequest */
- public String getServletPath() {
- return servletPath;
- }
- };
- childResource.service(childRequest, r2);
- return;
- }
- }
-
- RestRequest req = createRequest(r1);
- RestResponse res = createResponse(req, r2);
- String method = req.getMethod();
- String methodUC = method.toUpperCase(Locale.ENGLISH);
-
- StreamResource r = null;
- if (pathInfo != null) {
- String p = pathInfo.substring(1);
- if (p.equals("favicon.ico"))
- r = favIcon;
- else if (p.equals("style.css"))
- r = styleSheet;
- else if (StringUtils.pathStartsWith(p, staticFilesPrefixes))
- r = resolveStaticFile(p);
- }
-
- if (r != null) {
- res.setStatus(SC_OK);
- res.setOutput(r);
- } else {
- // If the specified method has been defined in a subclass, invoke it.
- int rc = SC_METHOD_NOT_ALLOWED;
- if (restMethods.containsKey(methodUC)) {
- rc = restMethods.get(methodUC).invoke(method, pathInfo, this, req, res);
- } else if (restMethods.containsKey("*")) {
- rc = restMethods.get("*").invoke(method, pathInfo, this, req, res);
- }
-
- // If not invoked above, see if it's an OPTIONs request
- if (rc != SC_OK)
- handleNotFound(rc, req, res);
- }
-
- if (res.hasOutput()) {
- Object output = res.getOutput();
-
- // Do any class-level transforming.
- for (RestConverter converter : getConverters())
- output = converter.convert(req, output, getBeanContext().getClassMetaForObject(output));
-
- res.setOutput(output);
-
- // Now serialize the output if there was any.
- // Some subclasses may write to the OutputStream or Writer directly.
- handleResponse(req, res, output);
- }
-
- onSuccess(req, res, System.currentTimeMillis() - startTime);
-
- } catch (RestException e) {
- handleError(r1, r2, e);
- } catch (Throwable e) {
- handleError(r1, r2, new RestException(SC_INTERNAL_SERVER_ERROR, e));
- }
- log(FINE, "HTTP: [{0} {1}] finished in {2}ms", r1.getMethod(), r1.getRequestURI(), System.currentTimeMillis()-startTime);
- }
-
- /**
- * Handle the case where a matching method was not found.
- * <p>
- * Subclasses can override this method to provide a 2nd-chance for specifying a response.
- * The default implementation will simply throw an exception with an appropriate message.
- *
- * @param rc The HTTP response code.
- * @param req The HTTP request.
- * @param res The HTTP response.
- * @throws Exception
- */
- protected void handleNotFound(int rc, RestRequest req, RestResponse res) throws Exception {
- String pathInfo = req.getPathInfo();
- String methodUC = req.getMethod();
- String onPath = pathInfo == null ? " on no pathInfo" : format(" on path '%s'", pathInfo);
- if (rc == SC_NOT_FOUND)
- throw new RestException(rc, "Method ''{0}'' not found on resource with matching pattern{1}.", methodUC, onPath);
- else if (rc == SC_PRECONDITION_FAILED)
- throw new RestException(rc, "Method ''{0}'' not found on resource{1} with matching matcher.", methodUC, onPath);
- else if (rc == SC_METHOD_NOT_ALLOWED)
- throw new RestException(rc, "Method ''{0}'' not found on resource.", methodUC);
- else
- throw new ServletException("Invalid method response: " + rc);
- }
-
- private synchronized void handleError(HttpServletRequest req, HttpServletResponse res, RestException e) throws IOException {
- Integer c = 1;
- if (context.useStackTraceHashes) {
- int h = e.hashCode();
- c = stackTraceHashes.get(h);
- if (c == null)
- c = 1;
- else
- c++;
- stackTraceHashes.put(h, c);
- e.setOccurrence(c);
- }
- onError(req, res, e);
- renderError(req, res, e);
- }
-
- /**
- * Method for rendering response errors.
- * <p>
- * The default implementation renders a plain text English message, optionally with a stack trace
- * if {@link RestServletContext#REST_renderResponseStackTraces} is enabled.
- * <p>
- * Subclasses can override this method to provide their own custom error response handling.
- *
- * @param req The servlet request.
- * @param res The servlet response.
- * @param e The exception that occurred.
- * @throws IOException Can be thrown if a problem occurred trying to write to the output stream.
- */
- protected void renderError(HttpServletRequest req, HttpServletResponse res, RestException e) throws IOException {
-
- int status = e.getStatus();
- res.setStatus(status);
- res.setContentType("text/plain");
- res.setHeader("Content-Encoding", "identity");
- PrintWriter w = null;
- try {
- w = res.getWriter();
- } catch (IllegalStateException e2) {
- w = new PrintWriter(new OutputStreamWriter(res.getOutputStream(), IOUtils.UTF8));
- }
- String httpMessage = RestUtils.getHttpResponseText(status);
- if (httpMessage != null)
- w.append("HTTP ").append(String.valueOf(status)).append(": ").append(httpMessage).append("\n\n");
- if (context.renderResponseStackTraces)
- e.printStackTrace(w);
- else
- w.append(e.getFullStackMessage(true));
- w.flush();
- w.close();
- }
-
- /**
- * Callback method for logging errors during HTTP requests.
- * <p>
- * ypically, subclasses will override this method and log errors themselves.
- * <p>
- * The default implementation simply logs errors to the <code>RestServlet</code> logger.
- * <p>
- * Here's a typical implementation showing how stack trace hashing can be used to reduce log file sizes...
- * </p>
- * <p class='bcode'>
- * <jk>protected void</jk> onError(HttpServletRequest req, HttpServletResponse res, RestException e, <jk>boolean</jk> noTrace) {
- * String qs = req.getQueryString();
- * String msg = <js>"HTTP "</js> + req.getMethod() + <js>" "</js> + e.getStatus() + <js>" "</js> + req.getRequestURI() + (qs == <jk>null</jk> ? <js>""</js> : <js>"?"</js> + qs);
- * <jk>int</jk> c = e.getOccurrence();
- *
- * <jc>// REST_useStackTraceHashes is disabled, so we have to log the exception every time.</jc>
- * <jk>if</jk> (c == 0)
- * myLogger.log(Level.<jsf>WARNING</jsf>, <jsm>format</jsm>(<js>"[%s] %s"</js>, e.getStatus(), msg), e);
- *
- * <jc>// This is the first time we've countered this error, so log a stack trace
- * // unless ?noTrace was passed in as a URL parameter.</jc>
- * <jk>else if</jk> (c == 1 && ! noTrace)
- * myLogger.log(Level.<jsf>WARNING</jsf>, <jsm>format</jsm>(<js>"[%h.%s.%s] %s"</js>, e.hashCode(), e.getStatus(), c, msg), e);
- *
- * <jc>// This error occurred before.
- * // Only log the message, not the stack trace.</jc>
- * <jk>else</jk>
- * myLogger.log(Level.<jsf>WARNING</jsf>, <jsm>format</jsm>(<js>"[%h.%s.%s] %s, %s"</js>, e.hashCode(), e.getStatus(), c, msg, e.getLocalizedMessage()));
- * }
- * </p>
- *
- * @param req The servlet request object.
- * @param res The servlet response object.
- * @param e Exception indicating what error occurred.
- */
- protected void onError(HttpServletRequest req, HttpServletResponse res, RestException e) {
- if (shouldLog(req, res, e)) {
- String qs = req.getQueryString();
- String msg = "HTTP " + req.getMethod() + " " + e.getStatus() + " " + req.getRequestURI() + (qs == null ? "" : "?" + qs);
- int c = e.getOccurrence();
- if (shouldLogStackTrace(req, res, e)) {
- msg = '[' + Integer.toHexString(e.hashCode()) + '.' + e.getStatus() + '.' + c + "] " + msg;
- log(Level.WARNING, e, msg);
- } else {
- msg = '[' + Integer.toHexString(e.hashCode()) + '.' + e.getStatus() + '.' + c + "] " + msg + ", " + e.getLocalizedMessage();
- log(Level.WARNING, msg);
- }
- }
- }
-
- /**
- * Returns <jk>true</jk> if the specified exception should be logged.
- * <p>
- * Subclasses can override this method to provide their own logic for determining when exceptions are logged.
- * <p>
- * The default implementation will return <jk>false</jk> if <js>"noTrace=true"</js> is passed in the query string.
- *
- * @param req The HTTP request.
- * @param res The HTTP response.
- * @param e The exception.
- * @return <jk>true</jk> if exception should be logged.
- */
- protected boolean shouldLog(HttpServletRequest req, HttpServletResponse res, RestException e) {
- String q = req.getQueryString();
- return (q == null ? true : q.indexOf("noTrace=true") == -1);
- }
-
- /**
- * Returns <jk>true</jk> if a stack trace should be logged for this exception.
- * <p>
- * Subclasses can override this method to provide their own logic for determining when stack traces are logged.
- * <p>
- * The default implementation will only log a stack trace if {@link RestException#getOccurrence()} returns <code>1</code>
- * and the exception is not one of the following:
- * </p>
- * <ul>
- * <li>{@link HttpServletResponse#SC_UNAUTHORIZED}
- * <li>{@link HttpServletResponse#SC_FORBIDDEN}
- * <li>{@link HttpServletResponse#SC_NOT_FOUND}
- * </ul>
- *
- * @param req The HTTP request.
- * @param res The HTTP response.
- * @param e The exception.
- * @return <jk>true</jk> if stack trace should be logged.
- */
- protected boolean shouldLogStackTrace(HttpServletRequest req, HttpServletResponse res, RestException e) {
- if (e.getOccurrence() == 1) {
- switch (e.getStatus()) {
- case SC_UNAUTHORIZED:
- case SC_FORBIDDEN:
- case SC_NOT_FOUND: return false;
- default: return true;
- }
- }
- return false;
- }
-
- /**
- * Log a message.
- * <p>
- * Equivalent to calling <code>log(level, <jk>null</jk>, msg, args);</code>
- *
- * @param level The log level.
- * @param msg The message to log.
- * @param args Optional {@link MessageFormat}-style arguments.
- */
- protected void log(Level level, String msg, Object...args) {
- log(level, null, msg, args);
- }
-
- /**
- * Same as {@link #log(Level, String, Object...)} excepts runs the
- * arguments through {@link JsonSerializer#DEFAULT_LAX_READABLE}.
- * <p>
- * Serialization of arguments do not occur if message is not logged, so
- * it's safe to use this method from within debug log statements.
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bcode'>
- * logObjects(<jsf>DEBUG</jsf>, <js>"Pojo contents:\n{0}"</js>, myPojo);
- * </p>
- *
- * @param level The log level.
- * @param msg The message to log.
- * @param args Optional {@link MessageFormat}-style arguments.
- */
- protected void logObjects(Level level, String msg, Object...args) {
- for (int i = 0; i < args.length; i++)
- args[i] = JsonSerializer.DEFAULT_LAX_READABLE.toStringObject(args[i]);
- log(level, null, msg, args);
- }
-
- /**
- * Log a message to the logger returned by {@link #getLogger()}.
- * <p>
- * Subclasses can override this method if they wish to log messages using a library other than
- * Java Logging (e.g. Apache Commons Logging).
- *
- * @param level The log level.
- * @param cause The cause.
- * @param msg The message to log.
- * @param args Optional {@link MessageFormat}-style arguments.
- */
- protected void log(Level level, Throwable cause, String msg, Object...args) {
- JuneauLogger log = getLogger();
- if (args.length > 0)
- msg = MessageFormat.format(msg, args);
- log.log(level, msg, cause);
- }
-
- /**
- * Callback method for listening for successful completion of requests.
- * <p>
- * Subclasses can override this method for gathering performance statistics.
- * <p>
- * The default implementation does nothing.
- *
- * @param req The HTTP request.
- * @param res The HTTP response.
- * @param time The time in milliseconds it took to process the request.
- */
- protected void onSuccess(RestRequest req, RestResponse res, long time) {}
-
- /**
- * Callback method that gets invoked right before the REST Java method is invoked.
- * <p>
- * Subclasses can override this method to override request headers or set request-duration properties
- * before the Java method is invoked.
- *
- * @param req The HTTP servlet request object.
- * @throws RestException If any error occurs.
- */
- protected void onPreCall(RestRequest req) throws RestException {}
-
- /**
- * Callback method that gets invoked right after the REST Java method is invoked, but before
- * the serializer is invoked.
- * <p>
- * Subclasses can override this method to override request and response headers, or
- * set/override properties used by the serializer.
- *
- * @param req The HTTP servlet request object.
- * @param res The HTTP servlet response object.
- * @throws RestException If any error occurs.
- */
- protected void onPostCall(RestRequest req, RestResponse res) throws RestException {}
-
- /**
- * The main method for serializing POJOs passed in through the {@link RestResponse#setOutput(Object)} method.
- * <p>
- * Subclasses may override this method if they wish to modify the way the output is rendered, or support
- * other output formats.
- *
- * @param req The HTTP request.
- * @param res The HTTP response.
- * @param output The output to serialize in the response.
- * @throws IOException
- * @throws RestException
- */
- protected void handleResponse(RestRequest req, RestResponse res, Object output) throws IOException, RestException {
- // Loop until we find the correct handler for the POJO.
- for (ResponseHandler h : getResponseHandlers())
- if (h.handle(req, res, output))
- return;
- throw new RestException(SC_NOT_IMPLEMENTED, "No response handlers found to process output of type '"+(output == null ? null : output.getClass().getName())+"'");
- }
-
- @Override /* GenericServlet */
- public ServletConfig getServletConfig() {
- return servletConfig;
- }
-
- @Override /* GenericServlet */
- public void destroy() {
- for (RestServlet r : childResources.values())
- r.destroy();
- super.destroy();
- }
-
- /**
- * Resolve a static resource file.
- * <p>
- * Subclasses can override this method to provide their own way to resolve files.
- *
- * @param pathInfo The unencoded path info.
- * @return The resource, or <jk>null</jk> if the resource could not be resolved.
- * @throws IOException
- */
- protected 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 = getMimetypesFileTypeMap().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);
- }
-
- /**
- * Returns a list of valid {@code Accept} content types for this resource.
- * <p>
- * Typically used by subclasses during {@code OPTIONS} requests.
- * <p>
- * The default implementation resturns the list from {@link ParserGroup#getSupportedMediaTypes()}
- * from the parser group returned by {@link #getParsers()}.
- * <p>
- * Subclasses can override or expand this list as they see fit.
- *
- * @return The list of valid {@code Accept} content types for this resource.
- * @throws RestServletException
- */
- public Collection<MediaType> getSupportedAcceptTypes() throws RestServletException {
- return getParsers().getSupportedMediaTypes();
- }
-
- /**
- * Returns a list of valid {@code Content-Types} for input for this resource.
- * <p>
- * Typically used by subclasses during {@code OPTIONS} requests.
- * <p>
- * The default implementation resturns the list from {@link SerializerGroup#getSupportedMediaTypes()}
- * from the parser group returned by {@link #getSerializers()}.
- * <p>
- * Subclasses can override or expand this list as they see fit.
- *
- * @return The list of valid {@code Content-Type} header values for this resource.
- * @throws RestServletException
- */
- public Collection<MediaType> getSupportedContentTypes() throws RestServletException {
- return getSerializers().getSupportedMediaTypes();
- }
-
- /**
- * 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) {
- MethodMeta m = javaRestMethods.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.
- */
- public String getMethodDescription(String javaMethodName, RestRequest req) {
- MethodMeta m = javaRestMethods.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 (title != null)
- return vr.resolve(title);
- String title = msgs.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 (description != null)
- return vr.resolve(description);
- String description = msgs.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 (contact != null)
- return jp.parse(vr.resolve(contact), Contact.class);
- String contact = msgs.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 (license != null)
- return jp.parse(vr.resolve(license), License.class);
- String license = msgs.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 (termsOfService != null)
- return vr.resolve(termsOfService);
- String termsOfService = msgs.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 (version != null)
- return vr.resolve(version);
- String version = msgs.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 (tags != null)
- return jp.parse(vr.resolve(tags), ArrayList.class, Tag.class);
- String tags = msgs.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 (externalDocs != null)
- return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
- String externalDocs = msgs.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);
- }
- }
-
- /**
- * Returns the resource bundle identified by the {@link RestResource#messages() @RestResource.messages()} annotation for the default locale.
- *
- * @return The resource bundle. Never <jk>null</jk>.
- */
- public MessageBundle getMessages() {
- return msgs;
- }
-
- /**
- * Returns the resource bundle identified by the {@link RestResource#messages() @RestResource.messages()} annotation for the specified locale.
- *
- * @param locale The resource bundle locale.
- * @return The resource bundle. Never <jk>null</jk>.
- */
- public MessageBundle getMessages(Locale locale) {
- return msgs.getBundle(locale);
- }
-
- /**
- * Gets a localized message from the resource bundle identified by the {@link RestResource#messages() @RestResource.messages()} annotation.
- * <p>
- * If resource bundle location was not specified, or the resource bundle was not found,
- * returns the string <js>"{!!key}"</js>.
- * <p>
- * If message was not found in the resource bundle, returns the string <js>"{!key}"</js>.
- *
- * @param locale The client locale.
- * @param key The resource bundle key.
- * @param args Optional {@link MessageFormat}-style arguments.
- * @return The localized message.
- */
- public String getMessage(Locale locale, String key, Object...args) {
- return msgs.getString(locale, key, args);
- }
-
- /**
- * Programmatically adds the specified resource as a child to this resource.
- * <p>
- * This method can be used in a resources {@link #init()} method to define child resources
- * accessible through a child URL.
- * <p>
- * Typically, child methods are defined via {@link RestResource#children() @RestResource.children()}. However, this
- * method is provided to handle child resources determined at runtime.
- *
- * @param name The sub-URL under which this resource is accessible.<br>
- * For example, if the parent resource URL is <js>"/foo"</js>, and this name is <js>"bar"</js>, then
- * the child resource will be accessible via the URL <js>"/foo/bar"</js>.
- * @param resource The child resource.
- * @throws ServletException Thrown by the child init() method.
- */
- protected void addChildResource(String name, RestServlet resource) throws ServletException {
- resource.init(getServletConfig());
- childResources.put(name, resource);
- }
-
- /**
- * 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,RestServlet> getChildResources() {
- return Collections.unmodifiableMap(childResources);
- }
-
- /**
- * Returns the path for this servlet as defined by the {@link RestResource#path()} annotation
- * on this class 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() {
- if (path == null) {
- LinkedList<String> l = new LinkedList<String>();
- RestServlet r = this;
- while (r != null) {
- String p = r.findPath();
- if (p == null)
- break;
- l.addFirst(p);
- r = r.parentResource;
- }
- StringBuilder sb = new StringBuilder();
- for (String p : l)
- sb.append('/').append(p);
- path = sb.toString();
- }
- return path;
- }
-
- private String findPath() {
- List<RestResource> rrc = ReflectionUtils.findAnnotations(RestResource.class, getClass());
- for (RestResource rc : rrc) {
- String p = rc.path();
- if (StringUtils.startsWith(p, '/'))
- p = p.substring(1);
- if (! p.isEmpty())
- return p;
- }
- return null;
- }
-
- /**
- * Returns the config file for this servlet.
- * <p>
- * Subclasses can override this method to provide their own config file.
- * <p>
- * The default implementation uses the path defined by the {@link RestResource#config() @RestResource.config()} property resolved
- * by {@link ConfigMgr#DEFAULT}.
- * @param vrb
- *
- * @return The config file for this servlet.
- * @throws IOException
- */
- protected ConfigFile createConfigFile(VarResolverBuilder vrb) throws IOException {
- String cf = vrb.build().resolve(configPath);
- if (cf.isEmpty())
- return getConfigMgr().create();
- return getConfigMgr().get(cf);
- }
-
- /**
- * Creates the stylesheet for this servlet.
- * <p>
- * The stylesheet is made available on the path <js>"/servlet-path/style.css"</js>.
- * <p>
- * Subclasses can override this method to provide their own stylesheet.
- * <p>
- * The default implementation uses the {@link RestResource#stylesheet() @RestResource.stylesheet()} annotation
- * to determine the stylesheet name and then searches the classpath then working directory
- * for that stylesheet.
- *
- * @return The stylesheet to use for this servlet, or <jk>null</jk> if the stylesheet could not be found.
- * @throws IOException If stylesheet could not be loaded.
- */
- protected StreamResource createStyleSheet() throws IOException {
- for (RestResource r : restResourceAnnotationsChildFirst.values())
- if (! r.stylesheet().isEmpty()) {
- List<InputStream> contents = new ArrayList<InputStream>();
-
- for (String path : StringUtils.split(getVarResolver().resolve(r.stylesheet()), ','))
- contents.add(getResource(path, null));
-
- return new StreamResource(MediaType.forString("text/css"), contents.toArray());
- }
- return null;
- }
-
- /**
- * Creates the favicon for this servlet.
- * <p>
- * The favicon is made available on the path <js>"/servlet-path/favicon.ico"</js>.
- * <p>
- * Subclasses can override this method to provide their own favorites icon.
- * <p>
- * The default implementation uses the {@link RestResource#favicon() @RestResource.favicon()} annotation
- * to determine the file name and then searches the classpath then working directory
- * for that file.
- *
- * @return The icon file to use for this servlet.
- * @throws IOException If icon file could not be loaded.
- */
- protected StreamResource createFavIcon() throws IOException {
- for (RestResource r : restResourceAnnotationsChildFirst.values()) {
- if (! r.favicon().isEmpty()) {
- String path = getVarResolver().resolve(r.favicon());
- InputStream is = getResource(path, null);
- if (is != null) {
- try {
- return new StreamResource(MediaType.forString("image/x-icon"), is);
- } finally {
- is.close();
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Creates the static files map for this servlet.
- * <p>
- * This map defines static files that can be served up through subpaths on this servlet.
- * The map keys are subpaths (e.g. <js>"htdocs"</js>) and the values are locations to look in
- * the classpath and working directory for those files.
- * <p>
- * Subclasses can override this method to provide their own mappings.
- * <p>
- * The default implementation uses the {@link RestResource#staticFiles() @RestResource.staticFiles()} annotation
- * to determine the mappings.
- *
- * @return The list of static file mappings.
- * @throws ParseException
- */
- @SuppressWarnings("unchecked")
- protected Map<String,String> createStaticFilesMap() throws ParseException {
- Map<String,String> m = new LinkedHashMap<String,String>();
-
<TRUNCATED>