http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/244cc21a/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestServlet.java
----------------------------------------------------------------------
diff --git 
a/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestServlet.java
 
b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestServlet.java
new file mode 100755
index 0000000..9b5bbb8
--- /dev/null
+++ 
b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestServlet.java
@@ -0,0 +1,2795 @@
+/***************************************************************************************************************************
+ * 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.server;
+
+import static java.lang.String.*;
+import static java.util.logging.Level.*;
+import static javax.servlet.http.HttpServletResponse.*;
+import static org.apache.juneau.internal.ArrayUtils.*;
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.serializer.SerializerContext.*;
+import static org.apache.juneau.server.RestServlet.ParamType.*;
+import static org.apache.juneau.server.RestServletContext.*;
+import static org.apache.juneau.server.annotation.Inherit.*;
+
+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.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.serializer.*;
+import org.apache.juneau.server.annotation.*;
+import org.apache.juneau.server.annotation.Properties;
+import org.apache.juneau.server.annotation.Var;
+import org.apache.juneau.server.labels.*;
+import org.apache.juneau.server.response.*;
+import org.apache.juneau.server.vars.*;
+import org.apache.juneau.svl.*;
+import org.apache.juneau.svl.vars.*;
+import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * Servlet implementation of a REST resource.
+ * <p>
+ *     Refer to <a class='doclink' href='package-summary.html#TOC'>REST 
Servlet API</a> for information about using this class.
+ * </p>
+ *
+ * @author jbognar
+ */
+@SuppressWarnings({"rawtypes","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<?>[] transforms;
+       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 label="", description="";
+       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 = "";
+
+       RestServletContext context;
+
+       // In-memory cache of images and stylesheets in the 
org.apache.juneau.server.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");
+       {
+               varResolver = 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();
+                       
varResolver.setContextObject(ConfigFileVar.SESSION_config, configFile);
+               } catch (IOException e) {
+                       this.initException = e;
+               }
+       }
+
+       @Override /* Servlet */
+       public 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());
+                               }
+                               if (label.isEmpty())
+                                       label = r.label();
+                               if (description.isEmpty())
+                                       description = r.description();
+                               if (clientVersionHeader.isEmpty())
+                                       clientVersionHeader = 
r.clientVersionHeader();
+                       }
+                       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();
+                       transforms = createTransforms();
+                       context = 
ContextFactory.create().setProperties(properties).getContext(RestServletContext.class);
+                       beanContext = createBeanContext(properties, transforms);
+                       urlEncodingSerializer = 
createUrlEncodingSerializer(properties, transforms).lock();
+                       urlEncodingParser = createUrlEncodingParser(properties, 
transforms).lock();
+                       serializers = createSerializers(properties, 
transforms).lock();
+                       parsers = createParsers(properties, transforms).lock();
+                       converters = createConverters(properties);
+                       encoders = createEncoders(properties);
+                       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);
+                                       }
+                               }
+                       }
+
+                       for (ResourceMethod m : restMethods.values())
+                               m.complete();
+
+                       // Discover the child resources.
+                       childResources.putAll(createChildrenMap());
+
+                       for (RestServlet child : childResources.values())
+                               child.init(servletConfig);
+
+                       varResolver.addVars(
+                               LocalizationVar.class,
+                               RequestAttrVar.class,
+                               RequestParamVar.class,
+                               RequestVar.class,
+                               SerializedRequestAttrVar.class,
+                               SerializedRequestParamVar.class,
+                               ServletInitParamVar.class,
+                               UrlEncodeVar.class
+                       );
+
+               } 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());
+                       label = 
String.valueOf(initException.getLocalizedMessage());
+               } catch (ServletException e) {
+                       initException = e;
+                       log(SEVERE, e, "Servlet init error on class ''{0}''", 
getClass().getName());
+                       label = 
String.valueOf(initException.getLocalizedMessage());
+                       throw e;
+               } catch (Exception e) {
+                       initException = e;
+                       log(SEVERE, e, "Servlet init error on class ''{0}''", 
getClass().getName());
+                       label = 
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());
+                       label = 
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>
+        * <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.
+        * </p>
+        *
+        * @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>
+        * <p>
+        *      Subclasses can override this method to programatically create 
child resources
+        *              without using the {@link RestResource#children() 
@RestResource.children()} annotation.
+        * </p>
+        *
+        * @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>
+        * <p>
+        *      Default implementation simply returns the value from the {@link 
RestResource#children() @RestResource.children()} annotation.
+        * </p>
+        *
+        * @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>
+        * <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 POJO transforms associated with this servlet.
+        * <p>
+        * Subclasses can override this method to provide their own class-level 
POJO transforms for this servlet.
+        * <p>
+        * By default, returns the transforms specified through the {@link 
RestResource#transforms() @RestResource.transforms()} annotation in 
child-to-parent order.
+        *      (i.e. transforms will be applied in child-to-parent order with 
child annotations overriding parent annotations when
+        *      the same transforms are applied).
+        *
+        * @return The new set of transforms associated with this servet.
+        */
+       protected Class<?>[] createTransforms() {
+               List<Class<?>> l = new LinkedList<Class<?>>();
+
+               // Transforms are loaded in parent-to-child order to allow 
overrides.
+               for (RestResource r : 
restResourceAnnotationsChildFirst.values())
+                       for (Class c : r.transforms())
+                               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 transforms Servlet-level transforms returned by {@link 
#createTransforms()}.
+        * @return The new bean context.
+        * @throws Exception If bean context not be constructed for any reason.
+        */
+       protected BeanContext createBeanContext(ObjectMap properties, 
Class<?>[] transforms) throws Exception {
+               return 
ContextFactory.create().addTransforms(transforms).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 transforms Servlet-level transforms returned by {@link 
#createTransforms()}.
+        * @return The new URL-Encoding serializer.
+        * @throws Exception If the serializer could not be constructed for any 
reason.
+        */
+       protected UrlEncodingSerializer createUrlEncodingSerializer(ObjectMap 
properties, Class<?>[] transforms) throws Exception {
+               return new 
UrlEncodingSerializer().setProperties(properties).addTransforms(transforms);
+       }
+
+       /**
+        * 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 transforms Servlet-level transforms returned by {@link 
#createTransforms()}.
+        * @return The new URL-Encoding parser.
+        * @throws Exception If the parser could not be constructed for any 
reason.
+        */
+       protected UrlEncodingParser createUrlEncodingParser(ObjectMap 
properties, Class<?>[] transforms) throws Exception {
+               return new 
UrlEncodingParser().setProperties(properties).addTransforms(transforms);
+       }
+
+       /**
+        * Creates the serializer group containing serializers used for 
serializing output POJOs in HTTP responses.
+        * <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>.
+        * <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 transforms Servlet-level transforms returned by {@link 
#createTransforms()}.
+        * @return The group of serializers.
+        * @throws Exception If serializer group could not be constructed for 
any reason.
+        */
+       protected SerializerGroup createSerializers(ObjectMap properties, 
Class<?>[] transforms) throws Exception {
+               SerializerGroup g = new SerializerGroup();
+
+               // Serializers are loaded in parent-to-child order to allow 
overrides.
+               for (RestResource r : 
restResourceAnnotationsParentFirst.values())
+                       for (Class<? extends Serializer> c : 
reverse(r.serializers()))
+                               try {
+                                       g.append(c);
+                               } catch (Exception e) {
+                                       throw new 
RestServletException("Exception occurred while trying to instantiate Serializer 
''{0}''", c.getSimpleName()).initCause(e);
+                               }
+
+               g.setProperties(properties);
+               g.addTransforms(transforms);
+               return g;
+       }
+
+       /**
+        * Creates the parser group containing parsers used for parsing input 
into POJOs from HTTP requests.
+        * <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>.
+        * <p>
+        * By default, returns the parsers defined through {@link 
RestResource#parsers() @RestResource.parsers()} on this class
+        *      and all parent classes.
+        *
+        * @param properties Servlet-level properties returned by {@link 
#createProperties()}.
+        * @param transforms Servlet-level transforms returned by {@link 
#createTransforms()}.
+        * @return The group of parsers.
+        * @throws Exception If parser group could not be constructed for any 
reason.
+        */
+       protected ParserGroup createParsers(ObjectMap properties, Class<?>[] 
transforms) throws Exception {
+               ParserGroup g = new ParserGroup();
+
+               // Parsers are loaded in parent-to-child order to allow 
overrides.
+               for (RestResource r : 
restResourceAnnotationsParentFirst.values())
+                       for (Class<? extends Parser> p : reverse(r.parsers()))
+                               try {
+                                       g.append(p);
+                               } catch (Exception e) {
+                                       throw new 
RestServletException("Exception occurred while trying to instantiate Parser 
''{0}''", p.getSimpleName()).initCause(e);
+                               }
+
+               g.setProperties(properties);
+               g.addTransforms(transforms);
+               return g;
+       }
+
+       /**
+        * Creates the class-level converters associated with this servlet.
+        * <p>
+        * Subclasses can override this method to provide their own class-level 
converters for this servlet.
+        * <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).
+        *
+        * @param properties Servlet-level properties returned by {@link 
#createProperties()}.
+        * @return The new set of transforms associated with this servet.
+        * @throws RestServletException
+        */
+       protected RestConverter[] createConverters(ObjectMap properties) throws 
RestServletException {
+               List<RestConverter> l = new LinkedList<RestConverter>();
+
+               // 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()]);
+       }
+
+       /**
+        * 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 EncoderGroup createEncoders(ObjectMap properties) throws 
RestServletException {
+               EncoderGroup g = new 
EncoderGroup().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;
+       }
+
+       /**
+        * 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.
+        * <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
+        */
+       protected RestGuard[] createGuards(ObjectMap properties) throws 
RestServletException {
+               List<RestGuard> l = new LinkedList<RestGuard>();
+
+               // 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);
+                               }
+
+               return l.toArray(new RestGuard[l.size()]);
+       }
+
+       /**
+        * 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;
+       }
+
+       /**
+        * Creates the set of default request headers for this servlet.
+        * <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.
+        * <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).
+        *
+        * @param properties Servlet-level properties returned by {@link 
#createProperties()}.
+        * @return The new set of default request headers associated with this 
servet.
+        * @throws RestServletException
+        */
+       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;
+       }
+
+       /**
+        * Creates the set of default response headers for this servlet.
+        * <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.
+        * <p>
+        * Subclasses can override this method to provide their own class-level 
default response headers for this servlet.
+        * <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).
+        *
+        * @param properties Servlet-level properties returned by {@link 
#createProperties()}.
+        * @return The new set of default response headers associated with this 
servet.
+        * @throws RestServletException
+        */
+       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;
+       }
+
+       /**
+        * Creates the class-level response handlers associated with this 
servlet.
+        * <p>
+        * Subclasses can override this method to provide their own class-level 
response handlers for this servlet.
+        * <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).
+        *
+        * @param properties Servlet-level properties returned by {@link 
#createProperties()}.
+        * @return The new set of response handlers associated with this servet.
+        * @throws RestException
+        */
+       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
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Sets the parent of this resource.
+        *
+        * @param parent The parent of this resource.
+        */
+       protected void setParent(RestServlet parent) {
+               this.parentResource = parent;
+       }
+
+       /**
+        * Returns the parent of this resource.
+        *
+        * @return The parent of this resource, or <jk>null</jk> if resource 
has no parent.
+        */
+       public RestServlet getParent() {
+               return this.parentResource;
+       }
+
+       private 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};
+       }
+
+       /**
+        * 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.
+        * </p>
+        *
+        * @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);
+       }
+
+       /**
+        * 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.
+        * </p>
+        *
+        * @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);
+       }
+
+       /**
+        * Returns whether this resource class can provide an OPTIONS page.
+        * <p>
+        *      By default, returns <jk>false</jk>.
+        * </p>
+        * <p>
+        *      Subclasses can override this method to cause the 
<code>options</code> link to show up in the HTML serialized output.
+        * </p>
+        *
+        * @return <jk>true</jk> if this resource has implemented a {@code 
getOptions()} method.
+        */
+       public boolean hasOptionsPage() {
+               return false;
+       }
+
+       /**
+        * Specify a class-level property.
+        * <p>
+        *      Typically, properties in {@link RestServletContext} can be set 
in the {@link Servlet#init(ServletConfig)} method.
+        * </p>
+        *
+        * @param key The property name.
+        * @param value The property value.
+        * @return This object (for method chaining).
+        */
+       public synchronized RestServlet setProperty(String key, Object value) {
+               getProperties().put(key, value);
+               return this;
+       }
+
+       /**
+        * The main service method.
+        * <p>
+        *      Subclasses can optionally override this method if they want to 
tailor the behavior of requests.
+        * </p>
+        */
+       @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.
+        * </p>
+        *
+        * @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>
+        * <p>
+        *      Subclasses can override this method to provide their own custom 
error response handling.
+        * </p>
+        *
+        * @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>
+        *      Typically, subclasses will override this method and log errors 
themselves.
+        * <p>
+        * </p>
+        *      The default implementation simply logs errors to the 
<code>RestServlet</code> logger.
+        * </p>
+        * <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>
+        * <p>
+        *      The default implementation will return <jk>false</jk> if 
<js>"noTrace=true"</js> is passed in the query string.
+        * </p>
+        *
+        * @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>
+        * <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>
+        * </p>
+        *
+        * @param level The log level.
+        * @param msg The message to log.
+        * @param args {@link MessageFormat} style arguments in the message.
+        */
+       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.
+        *      </p>
+        *
+        * <dl>
+        *      <dt>Example:</dt>
+        *      <dd>
+        *              <p class='bcode'>
+        *      logObjects(<jsf>DEBUG</jsf>, <js>"Pojo contents:\n{0}"</js>, 
myPojo);
+        *              </p>
+        *      </dd>
+        * </dl>
+        *
+        * @param level The log level.
+        * @param msg The message to log.
+        * @param args {@link MessageFormat} style arguments in the message.
+        */
+       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).
+        * </p>
+        *
+        * @param level The log level.
+        * @param cause The cause.
+        * @param msg The message to log.
+        * @param args {@link MessageFormat} style arguments in the message.
+        */
+       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>
+        * <p>
+        *      The default implementation does nothing.
+        * </p>
+        *
+        * @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.
+        * </p>
+        *
+        * @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.
+        * </p>
+        *
+        * @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.
+        * </p>
+        *
+        * @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.
+        *      </p>
+        *
+        * @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);
+                                               if (is != null) {
+                                                       try {
+                                                               int i = 
p2.lastIndexOf('/');
+                                                               String name = 
(i == -1 ? p2 : p2.substring(i+1));
+                                                               String 
mediaType = getMimetypesFileTypeMap().getContentType(name);
+                                                               
staticFilesCache.put(pathInfo, new StreamResource(is, 
mediaType).setHeader("Cache-Control", "max-age=86400, public"));
+                                                               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>
+        * <p>
+        *      The default implementation resturns the list from {@link 
ParserGroup#getSupportedMediaTypes()}
+        *              from the parser group returned by {@link #getParsers()}.
+        * </p>
+        * <p>
+        *      Subclasses can override or expand this list as they see fit.
+        * </p>
+        *
+        * @return The list of valid {@code Accept} content types for this 
resource.
+        * @throws RestServletException
+        */
+       public Collection<String> 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>
+        * <p>
+        *      The default implementation resturns the list from {@link 
SerializerGroup#getSupportedMediaTypes()}
+        *              from the parser group returned by {@link 
#getSerializers()}.
+        * </p>
+        * <p>
+        *      Subclasses can override or expand this list as they see fit.
+        * </p>
+        *
+        * @return The list of valid {@code Content-Type} header values for 
this resource.
+        * @throws RestServletException
+        */
+       public Collection<String> getSupportedContentTypes() throws 
RestServletException {
+               return getSerializers().getSupportedMediaTypes();
+       }
+
+       /**
+        * Returns localized descriptions of all REST methods defined on this 
class that the user of the current
+        *      request is allowed to access.
+        * <p>
+        *      Useful for OPTIONS pages.
+        * </p>
+        * <p>
+        *      This method does not cache results, since it's expected to be 
called infrequently.
+        * </p>
+        *
+        * @param req The current request.
+        * @return Localized descriptions of all REST methods defined on this 
class.
+        * @throws RestServletException
+        */
+       public Collection<MethodDescription> getMethodDescriptions(RestRequest 
req) throws RestServletException {
+               List<MethodDescription> l = new LinkedList<MethodDescription>();
+               for (MethodMeta sm : javaRestMethods.values())
+                       if (sm.isRequestAllowed(req))
+                               l.add(getMethodDescription(sm.method, sm, req));
+               return l;
+       }
+
+       /**
+        * Returns the localized description of this REST resource.
+        * <p>
+        *      Subclasses can override this method to provide their own 
description.
+        * </p>
+        * <p>
+        *      The default implementation returns the description from the 
following locations (whichever matches first):
+        * </p>
+        * <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.
+        * </ol>
+        *
+        * @param req The current request.
+        * @return The localized description of this REST resource, or a blank 
string if no resource description was found.
+        */
+       public String getDescription(RestRequest req) {
+               if (! description.isEmpty())
+                       return req.getVarResolverSession().resolve(description);
+               String description = msgs.findFirstString(req.getLocale(), 
"description");
+               return (description == null ? "" : 
req.getVarResolverSession().resolve(description));
+       }
+
+       /**
+        * Returns the localized description of the specified java method on 
this servlet.
+        * <p>
+        *      Subclasses can override this method to provide their own 
description.
+        * </p>
+        * <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]</ck> property in resource 
bundle identified by {@link RestResource#messages() @RestResource.messages()}
+        *              annotation for this class, then any parent classes.
+        *      <li><ck>[javaMethodName]</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 label of this REST resource.
+        * <p>
+        *      Subclasses can override this method to provide their own 
description.
+        * </p>
+        * <p>
+        *      The default implementation returns the description from the 
following locations (whichever matches first):
+        * </p>
+        * <ol>
+        *      <li>{@link RestResource#label() @RestResourcel.label()} 
annotation on this class, and then any parent classes.
+        *      <li><ck>[ClassName].label</ck> property in resource bundle 
identified by {@link RestResource#messages() @ResourceBundle.messages()}
+        *              annotation for this class, then any parent classes.
+        *      <li><ck>label</ck> in resource bundle identified by {@link 
RestResource#messages() @RestResource.messages()}
+        *              annotation for this class, then any parent classes.
+        * </ol>
+        *
+        * @param req The current request.
+        * @return The localized description of this REST resource, or a blank 
string if no resource description was found.
+        */
+       public String getLabel(RestRequest req) {
+               if (! label.isEmpty())
+                       return req.getVarResolverSession().resolve(label);
+               String label = msgs.findFirstString(req.getLocale(), "label");
+               return (label == null ? "" : 
req.getVarResolverSession().resolve(label));
+       }
+
+       /**
+        * 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>
+        * <p>
+        *      If message was not found in the resource bundle, returns the 
string <js>"{!key}"</js>.
+        * </p>
+        *
+        * @param locale The client locale.
+        * @param key The resource bundle key.
+        * @param args Optional {@link java.text.MessageFormat} variable values 
to replace.
+        * @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>
+        * <p>
+        *      Typically, child methods are defined via {@link 
RestResource#children() @RestResource.children()}.  However, this
+        *      method is provided to handle child resources determined at 
runtime.
+        * </p>
+        *
+        * @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>
+        * <p>
+        *      Path always starts with <js>"/"</js>.
+        * </p>
+        *
+        * @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>
+        * <p>
+        *      The default implementation uses the path defined by the {@link 
RestResource#config() @RestResource.config()} property resolved
+        *              by {@link ConfigMgr#DEFAULT}.
+        * </p>
+        *
+        * @return The config file for this servlet.
+        * @throws IOException
+        */
+       protected ConfigFile createConfigFile() throws IOException {
+               String cf = varResolver.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>
+        * <p>
+        *      Subclasses can override this method to provide their own 
stylesheet.
+        * </p>
+        * <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.
+        * </p>
+        *
+        * @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()) {
+                               String path = 
getVarResolver().resolve(r.stylesheet());
+                               InputStream is = getResource(path);
+                               if (is != null) {
+                                       try {
+                                               return new StreamResource(is, 
"text/css");
+                                       } finally {
+                                               is.close();
+                                       }
+                               }
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Creates the favicon for this servlet.
+        * <p>
+        *      The favicon is made available on the path 
<js>"/servlet-path/favicon.ico"</js>.
+        * </p>
+        * <p>
+        *      Subclasses can override this method to provide their own 
favorites icon.
+        * </p>
+        * <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.
+        * </p>
+        *
+        * @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);
+                               if (is != null) {
+                                       try {
+                                               return new StreamResource(is, 
"image/x-icon");
+                                       } 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>
+        * <p>
+        *      Subclasses can override this method to provide their own 
mappings.
+        * </p>
+        * <p>
+        *      The default implementation uses the {@link 
RestResource#staticFiles() @RestResource.staticFiles()} annotation
+        *              to determine the mappings.
+        * </p>
+        *
+        * @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>();
+               for (RestResource r : 
restResourceAnnotationsParentFirst.values())
+                       if (! r.staticFiles().isEmpty())
+                               
m.putAll(JsonParser.DEFAULT.parseMap(getVarResolver().resolve(r.staticFiles()), 
LinkedHashMap.class, String.class, String.class));
+               return m;
+       }
+
+       /**
+        * Returns the config manager used to create the config file in {@link 
#createConfigFile()}.
+        * <p>
+        *      The default implementation return {@link ConfigMgr#DEFAULT}, 
but subclasses can override
+        *              this if they want to provide their own customized 
config manager.
+        * </p>
+        *
+        * @return The config file manager.
+        */
+       protected ConfigMgr getConfigMgr() {
+               return ConfigMgr.DEFAULT;
+       }
+
+       /**
+        * Returns the logger associated with this servlet.
+        * <p>
+        *      Subclasses can override this method to provide their own Java 
Logging logger.
+        * </p>
+        * <p>
+        *      Subclasses that use other logging libraries such as Apache 
Commons Logging should
+        *              override the {@link #log(Level, Throwable, String, 
Object...)} method instead.
+        * </p>
+        *
+        * @return The logger associated with this servlet.
+        */
+       protected JuneauLogger getLogger() {
+               if (logger == null)
+                       logger =  JuneauLogger.getLogger(getClass());
+               return logger;
+       }
+
+       private abstract class ResourceMethod {
+               abstract int invoke(String methodName, String pathInfo, 
RestServlet resource, RestRequest req, RestResponse res) throws RestException;
+
+               void complete() {
+                       // Do nothing by default.
+               }
+       }
+
+       static enum ParamType {
+               REQ, RES, ATTR, CONTENT, HEADER, METHOD, PARAM, QPARAM, 
HASPARAM, HASQPARAM, PATHREMAINDER, PROPS, MESSAGES;
+
+               boolean isOneOf(ParamType...pt) {
+                       for (ParamType ptt : pt)
+                               if (this == ptt)
+                                       return true;
+                       return false;
+               }
+       }
+
+       static class MethodParam {
+
+               ParamType paramType;
+               Type type;
+               String name = "";
+               boolean multiPart, plainParams;
+
+               MethodParam(MethodMeta mm, Type type, Method method, 
Annotation[] annotations) throws ServletException {
+                       this.type = type;
+                       boolean isClass = type instanceof Class;
+                       if (isClass && isParentClass(HttpServletRequest.class, 
(Class)type))
+                               paramType = REQ;
+                       else if (isClass && 
isParentClass(HttpServletResponse.class, (Class)type))
+                               paramType = RES;
+                       else for (Annotation a : annotations) {
+                               if (a instanceof Attr) {
+                                       Attr a2 = (Attr)a;
+                                       paramType = ATTR;
+                                       name = a2.value();
+                               } else if (a instanceof Header) {
+                                       Header h = (Header)a;
+                                       paramType = HEADER;
+                                       name = h.value();
+                               } else if (a instanceof Param) {
+                                       Param p = (Param)a;
+                                       if (p.multipart())
+                                               assertCollection(type, method);
+                                       paramType = PARAM;
+                                       multiPart = p.multipart();
+                                       plainParams = 
p.format().equals("INHERIT") ? mm.mPlainParams : p.format().equals("PLAIN");
+                                       name = p.value();
+                               } else if (a instanceof QParam) {
+                                       QParam p = (QParam)a;
+                                       if (p.multipart())
+                                               assertCollection(type, method);
+                                       paramType = QPARAM;
+                                       multiPart = p.multipart();
+                                       plainParams = 
p.format().equals("INHERIT") ? mm.mPlainParams : p.format().equals("PLAIN");
+                                       name = p.value();
+                               } else if (a instanceof HasParam) {
+                                       HasParam p = (HasParam)a;
+                                       paramType = HASPARAM;
+                                       name = p.value();
+                               } else if (a instanceof HasQParam) {
+                                       HasQParam p = (HasQParam)a;
+                                       paramType = HASQPARAM;
+                                       name = p.value();
+                               } else if (a instanceof Content) {
+                                       paramType = CONTENT;
+                               } else if (a instanceof 
org.apache.juneau.server.annotation.Method) {
+                                       paramType = METHOD;
+                                       if (type != String.class)
+                                               throw new 
ServletException("@Method parameters must be of type String");
+                               } else if (a instanceof PathRemainder) {
+                                       paramType = PATHREMAINDER;
+                                       if (type != String.class)
+                                               throw new 
ServletException("@PathRemainder parameters must be of type String");
+                               } else if (a instanceof Properties) {
+                                       paramType = PROPS;
+                                       name = "PROPERTIES";
+                               } else if (a instanceof Messages) {
+                                       paramType = MESSAGES;
+                                       name = "MESSAGES";
+                               }
+                       }
+                       if (paramType == null)
+                               paramType = ATTR;
+               }
+
+               /**
+                * Throws an exception if the specified type isn't an array or 
collection.
+                */
+               private void assertCollection(Type t, Method m) throws 
ServletException {
+                       ClassMeta<?> cm = BeanContext.DEFAULT.getClassMeta(t);
+                       if (! (cm.isArray() || cm.isCollection()))
+                               throw new ServletException("Use of multipart 
flag on parameter that's not an array or Collection on method" + m);
+               }
+
+               @SuppressWarnings("unchecked")
+               private Object getValue(RestRequest req, RestResponse res) 
throws Exception {
+                       BeanContext bc = req.getServlet().getBeanContext();
+                       switch(paramType) {
+                               case REQ:        return req;
+                               case RES:        return res;
+                               case ATTR:       return req.getAttribute(name, 
type);
+                               case CONTENT:    return req.getInput(type);
+                               case HEADER:     return req.getHeader(name, 
type);
+                               case METHOD:     return req.getMethod();
+                               case PARAM: {
+                                       if (multiPart)
+                                               return req.getParameters(name, 
type);
+                                       if (plainParams)
+                                               return 
bc.convertToType(req.getParameter(name), bc.getClassMeta(type));
+                                       return req.getParameter(name, type);
+                               }
+                               case QPARAM: {
+                                       if (multiPart)
+                                               return 
req.getQueryParameters(name, type);
+                                       if (plainParams)
+                                               return 
bc.convertToType(req.getQueryParameter(name), bc.getClassMeta(type));
+                                       return req.getQueryParameter(name, 
type);
+                               }
+                               case HASPARAM:   return 
bc.convertToType(req.hasParameter(name), bc.getClassMeta(type));
+                               case HASQPARAM:   return 
bc.convertToType(req.hasQueryParameter(name), bc.getClassMeta(type));
+                               case PATHREMAINDER:  return 
req.getPathRemainder();
+                               case PROPS: return res.getProperties();
+                               case MESSAGES:   return req.getResourceBundle();
+                       }
+                       return null;
+               }
+       }
+
+       /*
+        * Represents a single Java servlet method annotated with @RestMethod.
+        */
+       private class MethodMeta extends ResourceMethod implements 
Comparable<MethodMeta>  {
+               private String httpMethod;
+               private java.lang.reflect.Method method;
+               private UrlPathPattern pathPattern;
+               private MethodParam[] params;
+               private RestGuard[] guards;
+               private RestMatcher[] optionalMatchers, requiredMatchers;
+               private RestConverter[] mConverters;
+               private SerializerGroup mSerializers;                   // 
Method-level serializers
+               private ParserGroup mParsers;                           // 
Method-level parsers
+               private EncoderGroup mEncoders;                         // 
Method-level encoders
+               private UrlEncodingParser mUrlEncodingParser;           // 
Method-level URL parameter parser.
+               private UrlEncodingSerializer mUrlEncodingSerializer;   // 
Method-level URL parameter serializer.
+               private ObjectMap mProperties;                          // 
Method-level properties
+               private Map<String,String> mDefaultRequestHeaders;      // 
Method-level default request headers
+               private String mDefaultEncoding;
+               private boolean mPlainParams;
+               private String description;
+               private Integer priority;
+
+               private MethodMeta(java.lang.reflect.Method method) throws 
RestServletException {
+                       try {
+                               this.method = method;
+
+                               RestMethod m = 
method.getAnnotation(RestMethod.class);
+                               if (m == null)
+                                       throw new 
RestServletException("@RestMethod annotation not found on method ''{0}.{1}''", 
method.getDeclaringClass().getName(), method.getName());
+
+                               this.description = m.description();
+                               this.mSerializers = getSerializers();
+                               this.mParsers = getParsers();
+                               this.mUrlEncodingParser = 
getUrlEncodingParser();
+                               this.mUrlEncodingSerializer = 
getUrlEncodingSerializer();
+                               this.mProperties = getProperties();
+                               this.mEncoders = getEncoders();
+
+                               ArrayList<Inherit> si = new 
ArrayList<Inherit>(Arrays.asList(m.serializersInherit()));
+                               ArrayList<Inherit> pi = new 
ArrayList<Inherit>(Arrays.asList(m.parsersInherit()));
+
+                               if (m.serializers().length > 0 || 
m.parsers().length > 0 || m.properties().length > 0 || m.transforms().length > 
0) {
+                                       mSerializers = 
(si.contains(SERIALIZERS) || m.serializers().length == 0 ? mSerializers.clone() 
: new SerializerGroup());
+                                       mParsers = (pi.contains(PARSERS) || 
m.parsers().length == 0 ? mParsers.clone() : new ParserGroup());
+                                       mUrlEncodingParser = 
mUrlEncodingParser.clone();
+                               }
+
+                               httpMethod = 
m.name().toUpperCase(Locale.ENGLISH);
+                               if (httpMethod.equals("") && 
method.getName().startsWith("do"))
+                                       httpMethod = 
method.getName().substring(2).toUpperCase(Locale.ENGLISH);
+                               if (httpMethod.equals(""))
+                                       throw new 
RestServletException("@RestMethod name not specified on method ''{0}.{1}''", 
method.getDeclaringClass().getName(), method.getName());
+                               if (httpMethod.equals("METHOD"))
+                                       httpMethod = "*";
+
+                               priority = m.priority();
+
+                               String p = m.path();
+                               mConverters = new 
RestConverter[m.converters().length];
+                               for (int i = 0; i < mConverters.length; i++)
+                                       mConverters[i] = 
m.converters()[i].newInstance();
+
+                               guards = new RestGuard[m.guards().length];
+                               for (int i = 0; i < guards.length; i++)
+                                       guards[i] = m.guards()[i].newInstance();
+
+                               List<RestMatcher> optionalMatchers = new 
LinkedList<RestMatcher>(), requiredMatchers = new LinkedList<RestMatcher>();
+                               for (int i = 0; i < m.matchers().length; i++) {
+                                       Class<? extends RestMatcher> c = 
m.matchers()[i];
+                                       RestMatcher matcher = null;
+                                       if 
(ClassUtils.isParentClass(RestMatcherReflecting.class, c))
+                                               matcher = 
c.getConstructor(RestServlet.class, Method.class).newInstance(RestServlet.this, 
method);
+                                       else
+                                               matcher = c.newInstance();
+                                       if (matcher.mustMatch())
+                                               requiredMatchers.add(matcher);
+                                       else
+                                               optionalMatchers.add(matcher);
+                               }
+                               if (! m.clientVersion().isEmpty())
+                                       requiredMatchers.add(new 
ClientVersionMatcher(RestServlet.this, method));
+
+                               this.requiredMatchers = 
requiredMatchers.toArray(new RestMatcher[requiredMatchers.size()]);
+                               this.optionalMatchers = 
optionalMatchers.toArray(new RestMatcher[optionalMatchers.size()]);
+
+                               if (m.serializers().length > 0) {
+                                       mSerializers.append(m.serializers());
+                                       if (si.contains(TRANSFORMS))
+                                               
mSerializers.addTransforms(getTransforms());
+                                       if (si.contains(PROPERTIES))
+                                               
mSerializers.setProperties(getProperties());
+                               }
+
+                               if (m.parsers().length > 0) {
+                                       mParsers.append(m.parsers());
+                                       if (pi.contains(TRANSFORMS))
+                                               
mParsers.addTransforms(getTransforms());
+                                       if (pi.contains(PROPERTIES))
+                                               
mParsers.setProperties(getProperties());
+                               }
+
+                               if (m.properties().length > 0) {
+                                       mProperties = new 
ObjectMap().setInner(getProperties());
+                                       for (Property p1 : m.properties()) {
+                                               String n = p1.name(), v = 
p1.value();
+                                               mProperties.put(n, v);
+                                               mSerializers.setProperty(n, v);
+                                               mParsers.setProperty(n, v);
+                                               
mUrlEncodingParser.setProperty(n, v);
+                                       }
+                               }
+
+                               if (m.transforms().length > 0) {
+                                       
mSerializers.addTransforms(m.transforms());
+                                       mParsers.addTransforms(m.transforms());
+                                       
mUrlEncodingParser.addTransforms(m.transforms());
+                               }
+
+                               if (m.encoders().length > 0 || ! 
m.inheritEncoders()) {
+                                       EncoderGroup g = new EncoderGroup();
+                                       if (m.inheritEncoders())
+                                               g.append(mEncoders);
+                                       else
+                                               
g.append(IdentityEncoder.INSTANCE);
+
+                                       for (Class<? extends Encoder> c : 
m.encoders()) {
+                                               try {
+                                                       g.append(c);
+                                               } catch (Exception e) {
+                                                       throw new 
RestServletException("Exception occurred while trying to instantiate Encoder 
''{0}''", c.getSimpleName()).initCause(e);
+                                               }
+                                       }
+                                       mEncoders = g;
+                               }
+
+                               mDefaultRequestHeaders = new 
TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
+                               for (String s : m.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);
+                                       mDefaultRequestHeaders.put(h[0], h[1]);
+                               }
+
+                               mDefaultEncoding = 
mProperties.getString(REST_defaultCharset, 
RestServlet.this.context.defaultCharset);
+                               String paramFormat = 
mProperties.getString(REST_paramFormat, RestServlet.this.context.paramFormat);
+                               mPlainParams = paramFormat.equals("PLAIN");
+
+                               pathPattern = new UrlPathPattern(p);
+
+                               int attrIdx = 0;
+                               Type[] pt = method.getGenericParameterTypes();
+                               Annotation[][] pa = 
method.getParameterAnnotations();
+                               params = new MethodParam[pt.length];
+                               for (int i = 0; i < params.length; i++) {
+                                       params[i] = new MethodParam(this, 
pt[i], method, pa[i]);
+                                       if (params[i].paramType == ATTR && 
params[i].name.isEmpty()) {
+                                               if (pathPattern.vars.length <= 
attrIdx)
+                                                       throw new 
RestServletException("Number of attribute parameters in method ''{0}'' exceeds 
the number of URL pattern variables.", method.getName());
+                                               params[i].name = 
pathPattern.vars[attrIdx++];
+                                       }
+                               }
+
+                               mSerializers.lock();
+                               mParsers.lock();
+                               mUrlEncodingParser.lock();
+
+                               // Need this to access methods in anonymous 
inner classes.
+                               method.setAccessible(true);
+                       } catch (Exception e) {
+                               throw new RestServletException("Exception 
occurred while initializing method ''{0}''", method.getName()).initCause(e);
+                       }
+               }
+
+               private String getDescription(RestRequest req) {
+                       if (! description.isEmpty())
+                               return 
req.getVarResolverSession().resolve(description);
+                       String description = 
msgs.findFirstString(req.getLocale(), method.getName());
+                       return (description == null ? "" : 
req.getVarResolverSession().resolve(description));
+               }
+
+               private boolean isRequestAllowed(RestRequest req) {
+                       for (RestGuard guard : guards) {
+                               req.javaMethod = method;
+                               if (! guard.isRequestAllowed(req))
+                                       return false;
+                       }
+                       return true;
+               }
+
+               @Override /* ResourceMethod */
+               int invoke(String methodName, String pathInfo, RestServlet 
resource, RestRequest req, RestResponse res) throws RestException {
+
+                       String[] patternVals = pathPattern.match(pathInfo);
+                       if (patternVals == null)
+                               return SC_NOT_FOUND;
+
+                       String remainder = null;
+                       if (patternVals.length > pathPattern.vars.length)
+                               remainder = 
patternVals[pathPattern.vars.length];
+                       for (int i = 0; i < pathPattern.vars.length; i++)
+                               req.setAttribute(pathPattern.vars[i], 
patternVals[i]);
+
+                       req.init(method, remainder, 
createRequestProperties(mProperties, req), mDefaultRequestHeaders, 
mDefaultEncoding, mSerializers, mParsers, mUrlEncodingParser);
+                       res.init(req.getProperties(), mDefaultEncoding, 
mSerializers, mUrlEncodingSerializer, mEncoders);
+
+                       // Class-level guards
+                       for (RestGuard guard : getGuards())
+                               if (! guard.guard(req, res))
+                                       return SC_UNAUTHORIZED;
+
+                       // If the method implements matchers, test them.
+                       for (RestMatcher m : requiredMatchers)
+                               if (! m.matches(req))
+                                       return SC_PRECONDITION_FAILED;
+                       if (optionalMatchers.length > 0) {
+                               boolean matches = false;
+                               for (RestMatcher m : optionalMatchers)
+                                       matches |= m.matches(req);
+                               if (! matches)
+                                       return SC_PRECONDITION_FAILED;
+                       }
+
+                       onPreCall(req);
+
+                       Object[] args = new Object[params.length];
+                       for (int i = 0; i < params.length; i++) {
+                               try {
+                                       args[i] = params[i].getValue(req, res);
+                               } catch (RestException e) {
+                                       throw e;
+                               } catch (Exception e) {
+                                       throw new RestException(SC_BAD_REQUEST,
+                                               "Invalid data conversion.  
Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.",
+                                               params[i].paramType.name(), 
params[i].name, params[i].type, method.getDeclaringClass().getName(), 
method.getName()
+                                       ).initCause(e);
+                               }
+                       }
+
+                       try {
+
+                               for (RestGuard guard : guards)
+                                       if (! guard.guard(req, res))
+                                               return SC_OK;
+
+                               Object output = method.invoke(resource, args);
+                               if (! method.getReturnType().equals(Void.TYPE))
+                                       if (output != null || ! 
res.getOutputStreamCalled())
+                                               res.setOutput(output);
+
+                               onPostCall(req, res);
+
+                               if (res.hasOutput()) {
+                                       output = res.getOutput();
+                                       for (RestConverter converter : 
mConverters)
+                                               output = converter.convert(req, 
output, getBeanContext().getClassMetaForObject(output));
+                                       res.setOutput(output);
+                               }
+                       } catch (IllegalArgumentException e) {
+                               throw new RestException(SC_BAD_REQUEST,
+                                       "Invalid argument type passed to the 
following method: ''{0}''.\n\tArgument types: {1}",
+                                       method.toString(), 
ClassUtils.getReadableClassNames(args)
+                               );
+                       } catch (InvocationTargetException e) {
+                               Throwable e2 = e.getTargetException();          
// Get the throwable thrown from the doX() method.
+                               if (e2 instanceof RestException)
+                                       throw (RestException)e2;
+                               if (e2 instanceof ParseException)
+                                       throw new RestException(SC_BAD_REQUEST, 
e2);
+                               if (e2 instanceof 
InvalidDataConversionException)
+                                       throw new RestException(SC_BAD_REQUEST, 
e2);
+                               throw new 
RestException(SC_INTERNAL_SERVER_ERROR, e2);
+                       } catch (RestException e) {
+                               throw e;
+                       } catch (Exception e) {
+                               throw new 
RestException(SC_INTERNAL_SERVER_ERROR, e);
+                       }
+                       return SC_OK;
+               }
+
+               @Override /* Object */
+               public String toString() {
+                       return "SimpleMethod: name=" + httpMethod + ", path=" + 
pathPattern.patternString;
+               }
+
+               /*
+                * compareTo() method is used to keep SimpleMethods ordered in 
the MultiMethod.tempCache list.
+                * It maintains the order in which matches are made during 
requests.
+                */
+               @Override /* Comparable */
+               public int compareTo(MethodMeta o) {
+                       int c;
+
+                       c = priority.compareTo(o.priority);
+                       if (c != 0)
+                               return c;
+
+                       c = pathPattern.compareTo(o.pathPattern);
+                       if (c != 0)
+                               return c;
+
+                       c = Utils.compare(o.requiredMatchers.length, 
requiredMatchers.length);
+                       if (c != 0)
+                               return c;
+
+                       c = Utils.compare(o.optionalMatchers.length, 
optionalMatchers.length);
+                       if (c != 0)
+                               return c;
+
+                       c = Utils.compare(o.guards.length, guards.length);
+                       if (c != 0)
+                               return c;
+
+                       return 0;
+               }
+
+               @Override /* Object */
+               public boolean equals(Object o) {
+                       if (! (o instanceof MethodMeta))
+                               return false;
+                       return (compareTo((MethodMeta)o) == 0);
+               }
+
+               @Override /* Object */
+               public int hashCode() {
+                       return super.hashCode();
+               }
+       }
+
+       /*
+        * Represents a group of SimpleMethods that all belong to the same HTTP 
method (e.g. "GET").
+        */
+       private class MultiMethod extends ResourceMethod {
+               MethodMeta[] childMethods;
+               List<MethodMeta> tempCache = new LinkedList<MethodMeta>();
+               Set<String> collisions = new HashSet<String>();
+
+               private MultiMethod(MethodMeta... simpleMethods) throws 
RestServletException {
+                       for (MethodMeta m : simpleMethods)
+                               addSimpleMethod(m);
+               }
+
+               private void addSimpleMethod(MethodMeta m) throws 
RestServletException {
+                       if (m.guards.length == 0 && m.requiredMatchers.length 
== 0 && m.optionalMatchers.length == 0) {
+                               String p = m.httpMethod + ":" + 
m.pathPattern.toRegEx();
+                               if (collisions.contains(p))
+                                       throw new 
RestServletException("Duplicate Java methods assigned to the same 
method/pattern:  method=''{0}'', path=''{1}''", m.httpMethod, m.pathPattern);
+                               collisions.add(p);
+                       }
+                       tempCache.add(m);
+               }
+
+               @Override /* ResourceMethod */
+               void complete() {
+                       Collections.sort(tempCache);
+                       collisions = null;
+                       childMethods = tempCache.toArray(new 
MethodMeta[tempCache.size()]);
+               }
+
+               @Override /* ResourceMethod */
+               int invoke(String methodName, String pathInfo, RestServlet 
resource, RestRequest req, RestResponse res) throws RestException {
+                       int maxRc = 0;
+                       for (MethodMeta m : childMethods) {
+                               int rc = m.invoke(methodName, pathInfo, 
resource, req, res);
+                               //if (rc == SC_UNAUTHORIZED)
+                               //      return SC_UNAUTHORIZED;
+                               if (rc == SC_OK)
+                                       return SC_OK;
+                               maxRc = Math.max(maxRc, rc);
+                       }
+                       return maxRc;
+               }
+
+               @Override /* Object */
+               public String toString() {
+                       StringBuilder sb = new StringBuilder("MultiMethod: 
[\n");
+                       for (MethodMeta sm : childMethods)
+                               sb.append("\t" + sm + "\n");
+                       sb.append("]");
+                       return sb.toString();
+               }
+       }
+
+       /**
+        * Returns the method description for the specified method for the 
OPTIONS page of this servlet.
+        * <p>
+        *      Subclasses can

<TRUNCATED>

Reply via email to