You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/03/17 02:17:10 UTC

[5/7] incubator-juneau git commit: Allow @RestResource/@RestMethod annotations to be used on any classes.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/07843d64/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java
new file mode 100644
index 0000000..8e10182
--- /dev/null
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java
@@ -0,0 +1,1153 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest;
+
+import static org.apache.juneau.internal.ArrayUtils.*;
+import static org.apache.juneau.serializer.SerializerContext.*;
+
+import java.io.*;
+import java.util.*;
+
+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.parser.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.response.*;
+import org.apache.juneau.rest.vars.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+import org.apache.juneau.svl.vars.*;
+
+/**
+ * Defines the initial configuration of a <code>RestServlet</code> or <code>@RestResource</code> annotated object.
+ * <p>
+ * An extension of the {@link ServletConfig} object used during servlet initialization.
+ * <p>
+ * Provides access to the following initialized resources:
+ * <ul>
+ * 	<li>{@link #getConfigFile()} - The external configuration file for this resource.
+ * 	<li>{@link #getProperties()} - The modifiable configuration properties for this resource.
+ * 	<li>{@link #getVarResolverBuilder()} - The variable resolver for this resource.
+ * </ul>
+ * <p>
+ * Methods are provided for overriding or augmenting the information provided by the <ja>@RestResource</ja> annotation.
+ * In general, most information provided in the <ja>@RestResource</ja> annotation can be specified programmatically
+ * through calls on this object.
+ * <p>
+ * To interact with this object, simply implement the following init method in your resource class:
+ * <p class='bcode'>
+ * 	<jk>public synchronized void</jk> init(RestConfig config) <jk>throws</jk> Exception {
+ * 		config.addPojoSwaps(CalendarSwap.<jsf>RFC2822DTZ</jsf>.<jk>class</jk>);
+ * 		config.setProperty(<jsf>PARSER_debug</jsf>, <jk>true</jk>);
+ * 		<jk>super</jk>.init(config); <jc>// Make sure this is the last line! (or just leave it out entirely)</jc>
+ * 	}
+ * </p>
+ * <p>
+ * Note that this method is identical to {@link HttpServlet#init(ServletConfig)} except you get access to
+ * this object instead.  Also, this method can throw any exception, not just a {@link ServletException}.
+ * <p>
+ * The parent <code>init(RestServletConfig)</code> method will construct a read-only {@link RestContext} object
+ * that contains a snapshot of these settings.  If you call <code><jk>super</jk>.init(RestServletConfig)<code> before
+ * you modify this config object, you won't see the changes!
+ */
+@SuppressWarnings("hiding")
+public class RestConfig implements ServletConfig {
+
+	final ServletConfig inner;
+
+	//---------------------------------------------------------------------------
+	// The following fields are meant to be modifiable.
+	// They should not be declared final.
+	// Read-only snapshots of these will be made in RestServletContext.
+	//---------------------------------------------------------------------------
+
+	ObjectMap properties;
+	ConfigFile configFile;
+	VarResolverBuilder varResolverBuilder;
+
+	List<Class<?>>
+		beanFilters = new ArrayList<Class<?>>(),
+		pojoSwaps = new ArrayList<Class<?>>();
+	SerializerGroupBuilder serializers = new SerializerGroupBuilder();
+	ParserGroupBuilder parsers = new ParserGroupBuilder();
+	EncoderGroupBuilder encoders = new EncoderGroupBuilder().append(IdentityEncoder.INSTANCE);
+	List<Object> converters = new ArrayList<Object>();
+	List<Object> guards = new ArrayList<Object>();
+	MimetypesFileTypeMap mimeTypes = new MimetypesFileTypeMap();
+	Map<String,String> defaultRequestHeaders = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
+	Map<String,Object> defaultResponseHeaders = new LinkedHashMap<String,Object>();
+	List<Object> responseHandlers = new ArrayList<Object>();
+	List<Object> childResources = new ArrayList<Object>();
+	List<MediaType> supportedContentTypes, supportedAcceptTypes;
+	List<Object> styleSheets;
+	Object favIcon;
+	List<Object> staticFiles;
+	RestContext parentContext;
+	String path;
+	String clientVersionHeader = "X-Client-Version";
+
+	Object resourceResolver = RestResourceResolver.class;
+	Object logger = RestLogger.Normal.class;
+	Object callHandler = RestCallHandler.class;
+	Object infoProvider = RestInfoProvider.class;
+
+	/**
+	 * Constructor.
+	 * @param config The servlet config passed into the servlet by the servlet container.
+	 * @param resource The class annotated with <ja>@RestResource</ja>.
+	 * @throws ServletException Something bad happened.
+	 */
+	RestConfig(ServletConfig config, Class<?> resourceClass, RestContext parentContext) throws ServletException {
+		this.inner = config;
+		this.parentContext = parentContext;
+		try {
+
+			properties = new ObjectMap();
+			configFile = ConfigMgr.DEFAULT.create();
+			varResolverBuilder = new VarResolverBuilder()
+				.vars(
+					SystemPropertiesVar.class,
+					EnvVariablesVar.class,
+					ConfigFileVar.class,
+					IfVar.class,
+					SwitchVar.class
+				);
+
+			VarResolver vr = varResolverBuilder.build();
+
+			Map<Class<?>,RestResource> restResourceAnnotationsParentFirst = ReflectionUtils.findAnnotationsMapParentFirst(RestResource.class, resourceClass);
+
+			// Find our config file.  It's the last non-empty @RestResource.config().
+			String configPath = "";
+			for (RestResource r : restResourceAnnotationsParentFirst.values())
+				if (! r.config().isEmpty())
+					configPath = r.config();
+			String cf = vr.resolve(configPath);
+			if (! cf.isEmpty())
+				configFile = ConfigMgr.DEFAULT.get(cf);
+			configFile = configFile.getResolving(vr);
+
+			// Add our config file to the variable resolver.
+			varResolverBuilder.contextObject(ConfigFileVar.SESSION_config, configFile);
+			vr = varResolverBuilder.build();
+
+			// Add our servlet URI to our properties.
+			ServletContext ctx = config.getServletContext();
+			String ctxPath = ctx.getContextPath();
+			// Workaround for bug in Jetty that causes context path to always end in "null".
+			if (ctxPath.endsWith("null"))
+				ctxPath = ctxPath.substring(0, ctxPath.length()-4);
+			properties.put(SERIALIZER_relativeUriBase, ctxPath);
+
+			// Add the servlet init parameters to our properties.
+			for (Enumeration<String> ep = config.getInitParameterNames(); ep.hasMoreElements();) {
+				String p = ep.nextElement();
+				String initParam = config.getInitParameter(p);
+				properties.put(vr.resolve(p), vr.resolve(initParam));
+			}
+
+			// Load stuff from parent-to-child order.
+			// This allows child settings to overwrite parent settings.
+			for (Map.Entry<Class<?>,RestResource> e : restResourceAnnotationsParentFirst.entrySet()) {
+				Class<?> c = e.getKey();
+				RestResource r = e.getValue();
+				for (Property p : r.properties())
+					properties.append(vr.resolve(p.name()), vr.resolve(p.value()));
+				addSerializers(reverse(r.serializers()));  // TODO - why reverse?
+				addParsers(reverse(r.parsers()));  // TODO - why reverse?
+				addEncoders(reverse(r.encoders()));  // TODO - why reverse?
+				addDefaultRequestHeaders(r.defaultRequestHeaders());
+				addDefaultResponseHeaders(r.defaultResponseHeaders());
+				addResponseHandlers(r.responseHandlers());
+				addConverters(r.converters());
+				addGuards(reverse(r.guards()));
+				addChildResources(r.children());
+				addBeanFilters(r.beanFilters());
+				addPojoSwaps(r.pojoSwaps());
+				if (! r.stylesheet().isEmpty())
+					setStyleSheet(c, r.stylesheet());
+				if (! r.favicon().isEmpty())
+					setFavIcon(c, r.favicon());
+				if (! r.staticFiles().isEmpty())
+					addStaticFiles(c, r.staticFiles());
+				if (! r.path().isEmpty())
+					setPath(r.path());
+				if (! r.clientVersionHeader().isEmpty())
+					setClientVersionHeader(r.clientVersionHeader());
+				if (r.resourceResolver() != RestResourceResolver.class)
+					setResourceResolver(r.resourceResolver());
+				if (r.logger() != RestLogger.Normal.class)
+					setLogger(r.logger());
+				if (r.callHandler() != RestCallHandler.class)
+					setCallHandler(r.callHandler());
+				if (r.infoProvider() != RestInfoProvider.class)
+					setInfoProvider(r.infoProvider());
+			}
+
+			addResponseHandlers(
+				StreamableHandler.class,
+				WritableHandler.class,
+				ReaderHandler.class,
+				InputStreamHandler.class,
+				RedirectHandler.class,
+				DefaultHandler.class
+			);
+
+			addMimeTypes(
+				"text/css css CSS",
+				"text/html html htm HTML",
+				"text/plain txt text TXT",
+				"application/javascript js",
+				"image/png png",
+				"image/gif gif",
+				"application/xml xml XML",
+				"application/json json JSON"
+			);
+		} catch (Exception e) {
+			throw new ServletException(e);
+		}
+	}
+
+	/**
+	 * Adds the specified {@link Var} classes to this config.
+	 * <p>
+	 * These variables affect the variable resolver returned by {@link RestRequest#getVarResolverSession()} which is
+	 * used to resolve string variables of the form <js>"$X{...}"</js>.
+	 * <p>
+	 * By default, this config includes the following variables:
+	 * <ul class='spaced-list'>
+	 * 	<li>{@link SystemPropertiesVar}
+	 * 	<li>{@link EnvVariablesVar}
+	 * 	<li>{@link ConfigFileVar}
+	 * 	<li>{@link IfVar}
+	 * 	<li>{@link SwitchVar}
+	 * </ul>
+	 * <p>
+	 * Later during the construction of {@link RestContext}, we add the following variables:
+	 * <ul>
+	 * 	<li>{@link LocalizationVar}
+	 * 	<li>{@link RequestVar}
+	 * 	<li>{@link SerializedRequestAttrVar}
+	 * 	<li>{@link ServletInitParamVar}
+	 * 	<li>{@link UrlEncodeVar}
+	 * </ul>
+	 *
+	 * @param vars The {@link Var} classes to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addVars(Class<?>...vars) {
+		this.varResolverBuilder.vars(vars);
+		return this;
+	}
+
+	/**
+	 * Adds a var context object to this config.
+	 * <p>
+	 * Var context objects are read-only objects associated with the variable resolver for
+	 * vars that require external information.
+	 * <p>
+	 * For example, the {@link ConfigFileVar} needs access to this resource's {@link ConfigFile} through the {@link ConfigFileVar#SESSION_config}
+	 * object that can be specified as either a session object (temporary) or context object (permanent).
+	 * In this case, we call the following code to add it to the context map:
+	 * <p class='bcode'>
+	 * 	config.addVarContextObject(<jsf>SESSION_config</jsf>, configFile);
+	 * </p>
+	 *
+	 * @param name The context object key (i.e. the name that the Var class looks for).
+	 * @param object The context object.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addVarContextObject(String name, Object object) {
+		this.varResolverBuilder.contextObject(name, object);
+		return this;
+	}
+
+	/**
+	 * Overwrites the default config file with a custom config file.
+	 * <p>
+	 * By default, the config file is determined using the {@link RestResource#config() @RestResource.config()} annotation.
+	 * This method allows you to programmatically override it with your own custom config file.
+	 *
+	 * @param configFile The new config file.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setConfigFile(ConfigFile configFile) {
+		this.configFile = configFile;
+		return this;
+	}
+
+	/**
+	 * Sets a property on this resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#properties()} annotation.
+	 *
+	 * @param key The property name.
+	 * @param value The property value.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setProperty(String key, Object value) {
+		this.properties.put(key, value);
+		return this;
+	}
+
+	/**
+	 * Sets multiple properties on this resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#properties() @RestResource.properties()} annotation.
+	 * <p>
+	 * Values in the map are added to the existing properties and are overwritten if duplicates are found.
+	 *
+	 * @param properties The new properties to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setProperties(Map<String,Object> properties) {
+		this.properties.putAll(properties);
+		return this;
+	}
+
+	/**
+	 * Adds class-level bean filters to this resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#beanFilters() @RestResource.beanFilters()} annotation.
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 *
+	 * @param beanFilters The bean filters to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addBeanFilters(Class<?>...beanFilters) {
+		this.beanFilters.addAll(Arrays.asList(beanFilters));
+		return this;
+	}
+
+	/**
+	 * Adds class-level pojo swaps to this resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#pojoSwaps() @RestResource.pojoSwaps()} annotation.
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 *
+	 * @param pojoSwaps The pojo swaps to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addPojoSwaps(Class<?>...pojoSwaps) {
+		this.pojoSwaps.addAll(Arrays.asList(pojoSwaps));
+		return this;
+	}
+
+	/**
+	 * Adds class-level serializers to this resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#serializers() @RestResource.serializers()} annotation.
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 *
+	 * @param serializers The serializer classes to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addSerializers(Class<?>...serializers) {
+		this.serializers.append(serializers);
+		return this;
+	}
+
+	/**
+	 * Adds class-level serializers to this resource.
+	 * <p>
+	 * Same as {@link #addSerializers(Class...)} except allows you to pass in serializer instances.
+	 * The actual serializer ends up being the result of this operation using the bean filters, pojo swaps, and properties on this config:
+	 * <p class='bcode'>
+	 * 	serializer = serializer.builder().beanFilters(beanFilters).pojoSwaps(pojoSwaps).properties(properties).build();
+	 * </p>
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 *
+	 * @param serializers The serializers to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addSerializers(Serializer...serializers) {
+		this.serializers.append(serializers);
+		return this;
+	}
+
+	/**
+	 * Adds class-level parsers to this resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#parsers() @RestResource.parsers()} annotation.
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 *
+	 * @param parsers The parser classes to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addParsers(Class<?>...parsers) {
+		this.parsers.append(parsers);
+		return this;
+	}
+
+	/**
+	 * Adds class-level parsers to this resource.
+	 * <p>
+	 * Same as {@link #addParsers(Class...)} except allows you to pass in parser instances.
+	 * The actual parser ends up being the result of this operation using the bean filters, pojo swaps, and properties on this config:
+	 * <p class='bcode'>
+	 * 	parser = parser.builder().beanFilters(beanFilters).pojoSwaps(pojoSwaps).properties(properties).build();
+	 * </p>
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 *
+	 * @param parsers The parsers to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addParsers(Parser...parsers) {
+		this.parsers.append(parsers);
+		return this;
+	}
+
+	/**
+	 * Adds class-level encoders to this resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#encoders() @RestResource.encoders()} annotation.
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 * <p>
+	 * By default, only the {@link IdentityEncoder} is included in this list.
+	 *
+	 * @param encoders The parser classes to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addEncoders(Class<?>...encoders) {
+		this.encoders.append(encoders);
+		return this;
+	}
+
+	/**
+	 * Adds class-level encoders to this resource.
+	 * <p>
+	 * Same as {@link #addEncoders(Class...)} except allows you to pass in encoder instances.
+	 *
+	 * @param encoders The encoders to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addEncoders(Encoder...encoders) {
+		this.encoders.append(encoders);
+		return this;
+	}
+
+	/**
+	 * Adds class-level converters to this resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#converters() @RestResource.converters()} annotation.
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 * <p>
+	 * By default, this config includes the following converters:
+	 * <ul class='spaced-list'>
+	 * 	<li>{@link StreamableHandler}
+	 * 	<li>{@link WritableHandler}
+	 * 	<li>{@link ReaderHandler}
+	 * 	<li>{@link InputStreamHandler}
+	 * 	<li>{@link RedirectHandler}
+	 * 	<li>{@link DefaultHandler}
+	 * </ul>
+	 *
+	 * @param converters The converter classes to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addConverters(Class<?>...converters) {
+		this.converters.addAll(Arrays.asList(converters));
+		return this;
+	}
+
+	/**
+	 * Adds class-level encoders to this resource.
+	 * <p>
+	 * Same as {@link #addConverters(Class...)} except allows you to pass in converter instances.
+	 *
+	 * @param converters The converters to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addConverters(RestConverter...converters) {
+		this.converters.addAll(Arrays.asList(converters));
+		return this;
+	}
+
+	/**
+	 * Adds class-level guards to this resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#guards() @RestResource.guards()} annotation.
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 *
+	 * @param guards The guard classes to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addGuards(Class<?>...guards) {
+		this.guards.addAll(Arrays.asList(guards));
+		return this;
+	}
+
+	/**
+	 * Adds class-level guards to this resource.
+	 * <p>
+	 * Same as {@link #addGuards(Class...)} except allows you to pass in guard instances.
+	 *
+	 * @param guards The guards to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addGuards(RestGuard...guards) {
+		this.guards.addAll(Arrays.asList(guards));
+		return this;
+	}
+
+	/**
+	 * Adds MIME-type definitions.
+	 * <p>
+	 * These definitions are used in the following locations for setting the media type on responses:
+	 * <ul>
+	 * 	<li>{@link RestRequest#getReaderResource(String)}
+	 * 	<li>Static files resolved through {@link RestResource#staticFiles()}
+	 * </ul>
+	 * <p>
+	 * Refer to {@link MimetypesFileTypeMap#addMimeTypes(String)} for an explanation of the format.
+	 * <p>
+	 * By default, this config includes the following mime-type definitions:
+	 * <ul class='spaced-list'>
+	 * 	<li><js>"text/css css CSS"</js>
+	 * 	<li><js>"text/html html htm HTML"</js>
+	 * 	<li><js>"text/plain txt text TXT"</js>
+	 * 	<li><js>"application/javascript js"</js>
+	 * 	<li><js>"image/png png"</js>
+	 * 	<li><js>"image/gif gif"</js>
+	 * 	<li><js>"application/xml xml XML"</js>
+	 * 	<li><js>"application/json json JSON"</js>
+	 * </ul>
+	 *
+	 * @param mimeTypes The MIME-types to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addMimeTypes(String...mimeTypes) {
+		for (String mimeType : mimeTypes)
+			this.mimeTypes.addMimeTypes(mimeType);
+		return this;
+	}
+
+	/**
+	 * Adds class-level default HTTP request headers to this resource.
+	 * <p>
+	 * Default request headers are default values for when HTTP requests do not specify a header value.
+	 * For example, you can specify a default value for <code>Accept</code> if a request does not specify that header value.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#defaultRequestHeaders() @RestResource.defaultRequestHeaders()} annotation.
+	 *
+	 * @param name The HTTP header name.
+	 * @param value The HTTP header value.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addDefaultRequestHeader(String name, Object value) {
+		this.defaultRequestHeaders.put(name, StringUtils.toString(value));
+		return this;
+	}
+
+	/**
+	 * Adds class-level default HTTP request headers to this resource.
+	 * <p>
+	 * Default request headers are default values for when HTTP requests do not specify a header value.
+	 * For example, you can specify a default value for <code>Accept</code> if a request does not specify that header value.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#defaultRequestHeaders() @RestResource.defaultRequestHeaders()} annotation.
+	 *
+	 * @param headers HTTP headers of the form <js>"Name: Value"</js>.
+	 * @return This object (for method chaining).
+	 * @throws RestServletException If header string is not correctly formatted.
+	 */
+	public RestConfig addDefaultRequestHeaders(String...headers) throws RestServletException {
+		for (String header : headers) {
+			String[] h = RestUtils.parseHeader(header);
+			if (h == null)
+				throw new RestServletException("Invalid default request header specified: ''{0}''.  Must be in the format: ''Header-Name: header-value''", header);
+			addDefaultRequestHeader(h[0], h[1]);
+		}
+		return this;
+	}
+
+	/**
+	 * Adds class-level default HTTP response headers to this resource.
+	 * <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>
+	 * This is the programmatic equivalent to the {@link RestResource#defaultResponseHeaders() @RestResource.defaultResponseHeaders()} annotation.
+	 * <p>
+	 * Values are added AFTER those found in the annotation and therefore take precedence over those defined via the annotation.
+	 *
+	 * @param name The HTTP header name.
+	 * @param value The HTTP header value.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addDefaultResponseHeader(String name, Object value) {
+		this.defaultResponseHeaders.put(name, value);
+		return this;
+	}
+
+	/**
+	 * Adds class-level default HTTP response headers to this resource.
+	 * <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>
+	 * This is the programmatic equivalent to the {@link RestResource#defaultResponseHeaders() @RestResource.defaultResponseHeaders()} annotation.
+	 *
+	 * @param headers HTTP headers of the form <js>"Name: Value"</js>.
+	 * @return This object (for method chaining).
+	 * @throws RestServletException If header string is not correctly formatted.
+	 */
+	public RestConfig addDefaultResponseHeaders(String...headers) throws RestServletException {
+		for (String header : headers) {
+			String[] h = RestUtils.parseHeader(header);
+			if (h == null)
+				throw new RestServletException("Invalid default response header specified: ''{0}''.  Must be in the format: ''Header-Name: header-value''", header);
+			addDefaultResponseHeader(h[0], h[1]);
+		}
+		return this;
+	}
+
+	/**
+	 * Adds class-level response handler classes to this resource.
+	 * <p>
+	 * Response handlers are responsible for converting various POJOs returned by REST methods into actual HTTP responses.
+	 * <p>
+	 * By default, this config includes the following response handlers:
+	 * <ul class='spaced-list'>
+	 * 	<li>{@link StreamableHandler}
+	 * 	<li>{@link WritableHandler}
+	 * 	<li>{@link ReaderHandler}
+	 * 	<li>{@link InputStreamHandler}
+	 * 	<li>{@link RedirectHandler}
+	 * 	<li>{@link DefaultHandler}
+	 * </ul>
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#responseHandlers() @RestResource.responseHandlers()} annotation.
+	 *
+	 * @param responseHandlers The response handlers to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addResponseHandlers(Class<?>...responseHandlers) {
+		this.responseHandlers.addAll(Arrays.asList(responseHandlers));
+		return this;
+	}
+
+	/**
+	 * Adds class-level response handlers to this resource.
+	 * <p>
+	 * Same as {@link #addResponseHandlers(Class...)} except allows you to pass in response handler instances.
+	 *
+	 * @param responseHandlers The response handlers to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addResponseHandlers(ResponseHandler...responseHandlers) {
+		this.responseHandlers.addAll(Arrays.asList(responseHandlers));
+		return this;
+	}
+
+	/**
+	 * Adds a child resource to this resource.
+	 * <p>
+	 * Child resources are resources that are accessed under the path of the parent resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#children() @RestResource.children()} annotation.
+	 *
+	 * @param path The child path of the resource.  Must conform to {@link RestResource#path()} format.
+	 * @param child The child resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addChildResource(String path, Object child) {
+		this.childResources.add(new Pair<String,Object>(path, child));
+		return this;
+	}
+
+	/**
+	 * Add child resources to this resource.
+	 * <p>
+	 * Child resources are resources that are accessed under the path of the parent resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#children() @RestResource.children()} annotation.
+	 *
+	 * @param children The child resources to add to this resource.
+	 * Children must be annotated with {@link RestResource#path()} to identify the child path.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addChildResources(Object...children) {
+		this.childResources.addAll(Arrays.asList(children));
+		return this;
+	}
+
+	/**
+	 * Add child resources to this resource.
+	 * <p>
+	 * Child resources are resources that are accessed under the path of the parent resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#children() @RestResource.children()} annotation.
+	 *
+	 * @param children The child resources to add to this resource.
+	 * Children must be annotated with {@link RestResource#path()} to identify the child path.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addChildResources(Class<?>...children) {
+		this.childResources.addAll(Arrays.asList(children));
+		return this;
+	}
+
+	/**
+	 * Specifies the list of supported <code>Accept</code> media types for this resource.
+	 * <p>
+	 * This overrides the media types inferred from the parsers on this resource.
+	 * <p>
+	 * There is no annotation equivalent to this method call.
+	 *
+	 * @param mediaTypes The new list of media types supported by this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setSupportedAcceptTypes(String...mediaTypes) {
+		supportedAcceptTypes = new ArrayList<MediaType>();
+		for (String mediaType : mediaTypes)
+			supportedAcceptTypes.add(MediaType.forString(mediaType));
+		return this;
+	}
+
+	/**
+	 * Specifies the list of supported <code>Accept</code> media types for this resource.
+	 * <p>
+	 * This overrides the media types inferred from the parsers on this resource.
+	 * <p>
+	 * There is no annotation equivalent to this method call.
+	 *
+	 * @param mediaTypes The new list of media types supported by this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setSupportedAcceptTypes(MediaType...mediaTypes) {
+		supportedAcceptTypes = Arrays.asList(mediaTypes);
+		return this;
+	}
+
+	/**
+	 * Specifies the list of supported <code>Content-Type</code> media types for this resource.
+	 * <p>
+	 * This overrides the media types inferred from the serializers on this resource.
+	 * <p>
+	 * There is no annotation equivalent to this method call.
+	 *
+	 * @param mediaTypes The new list of media types supported by this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setSupportedContentTypes(String...mediaTypes) {
+		supportedContentTypes = new ArrayList<MediaType>();
+		for (String mediaType : mediaTypes)
+			supportedContentTypes.add(MediaType.forString(mediaType));
+		return this;
+	}
+
+	/**
+	 * Specifies the list of supported <code>Content-Type</code> media types for this resource.
+	 * <p>
+	 * This overrides the media types inferred from the serializers on this resource.
+	 * <p>
+	 * There is no annotation equivalent to this method call.
+	 *
+	 * @param mediaTypes The new list of media types supported by this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setSupportedContentTypes(MediaType...mediaTypes) {
+		supportedContentTypes = Arrays.asList(mediaTypes);
+		return this;
+	}
+
+	/**
+	 * Specifies the stylesheets that make up the contents of the page <js>"/resource-path/styles.css"</js>.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#stylesheet() @RestResource.stylesheet()} annotation.
+	 * <p>
+	 * The object types can be any of the following:
+	 * <ul>
+	 * 	<li>{@link InputStream}
+	 * 	<li>{@link Reader}
+	 * 	<li>{@link File}
+	 * 	<li>{@link CharSequence}
+	 * 	<li><code><jk>byte</jk>[]</code>
+	 * </ul>
+	 * The contents of all these stylesheets will be aggregated into a single page in the order they are specified in this list.
+	 *
+	 * @param styleSheets The new list of style sheets that make up the <code>styles.css</code> page.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setStyleSheet(Object...styleSheets) {
+		this.styleSheets = new ArrayList<Object>(Arrays.asList(styleSheets));
+		return this;
+	}
+
+	/**
+	 * Specifies the stylesheet that make up the contents of the page <js>"/resource-path/styles.css"</js>.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#stylesheet() @RestResource.stylesheet()} annotation.
+	 * <p>
+	 * Use this method to specify a resource located in the classpath.
+	 * This call uses the {@link Class#getResourceAsStream(String)} method to retrieve the stylesheet contents.
+	 *
+	 * @param resourceClass The resource class used to resolve the resource stream.
+	 * @param resourcePath The path passed to the {@link Class#getResourceAsStream(String)} method.
+	 * Can also be a path starting with <js>"file://"</js> denoting a location to pull from the file system.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setStyleSheet(Class<?> resourceClass, String resourcePath) {
+		this.styleSheets = new ArrayList<Object>();
+		this.styleSheets.add(new Pair<Class<?>,String>(resourceClass, resourcePath));
+		return this;
+	}
+
+	/**
+	 * Adds to the stylesheet that make up the contents of the page <js>"/resource-path/styles.css"</js>.
+	 * <p>
+	 * Same as {@link #setStyleSheet(Object...)} except appends to the existing list instead of replacing.
+	 *
+	 * @param styleSheets The list of style sheets to add that make up the <code>styles.css</code> page.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addStyleSheet(Object...styleSheets) {
+		if (this.styleSheets == null)
+			this.styleSheets = new ArrayList<Object>();
+		this.styleSheets.addAll(Arrays.asList(styleSheets));
+		return this;
+	}
+
+	/**
+	 * Adds to the stylesheet that make up the contents of the page <js>"/resource-path/styles.css"</js>.
+	 * <p>
+	 * Same as {@link #setStyleSheet(Class,String)} except appends to the existing list instead of replacing.
+	 *
+	 * @param resourceClass The resource class used to resolve the resource stream.
+	 * @param resourcePath The path passed to the {@link Class#getResourceAsStream(String)} method.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addStyleSheet(Class<?> resourceClass, String resourcePath) {
+		if (this.styleSheets == null)
+			this.styleSheets = new ArrayList<Object>();
+		this.styleSheets.add(new Pair<Class<?>,String>(resourceClass, resourcePath));
+		return this;
+	}
+
+	/**
+	 * Specifies the icon contents that make up the contents of the page <js>"/resource-path/favicon.ico"</js>.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#favicon() @RestResource.favicon()} annotation.
+	 * <p>
+	 * The object type can be any of the following:
+	 * <ul>
+	 * 	<li>{@link InputStream}
+	 * 	<li>{@link File}
+	 * 	<li><code><jk>byte</jk>[]</code>
+	 * </ul>
+	 *
+	 * @param favIcon The contents that make up the <code>favicon.ico</code> page.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setFavIcon(Object favIcon) {
+		this.favIcon = favIcon;
+		return this;
+	}
+
+	/**
+	 * Specifies the icon contents that make up the contents of the page <js>"/resource-path/favicon.ico"</js>.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#favicon() @RestResource.favicon()} annotation.
+	 * <p>
+	 * Use this method to specify a resource located in the classpath.
+	 * This call uses the {@link Class#getResourceAsStream(String)} method to retrieve the stylesheet contents.
+	 *
+	 * @param resourceClass The resource class used to resolve the resource stream.
+	 * @param resourcePath The path passed to the {@link Class#getResourceAsStream(String)} method.
+	 * Can also be a path starting with <js>"file://"</js> denoting a location to pull from the file system.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setFavIcon(Class<?> resourceClass, String resourcePath) {
+		this.favIcon = new Pair<Class<?>,String>(resourceClass, resourcePath);
+		return this;
+	}
+
+	/**
+	 * Appends to the static files resource map.
+	 * <p>
+	 * Use this method to specify resources located in the classpath to be served up as static files.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#staticFiles() @RestResource.staticFiles()} annotation.
+	 *
+	 * @param resourceClass The resource class used to resolve the resource streams.
+	 * @param staticFilesString A JSON string denoting a map of child URLs to classpath subdirectories.
+	 * For example, if this string is <js>"{htdocs:'docs'}"</js> with class <code>com.foo.MyResource</code>,
+	 * then URLs of the form <js>"/resource-path/htdocs/..."</js> will resolve to files located in the <code>com.foo.docs</code> package.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig addStaticFiles(Class<?> resourceClass, String staticFilesString) {
+		if (staticFiles == null)
+			staticFiles = new ArrayList<Object>();
+		staticFiles.add(new Pair<Class<?>,Object>(resourceClass, staticFilesString));
+		return this;
+	}
+
+	/**
+	 * Overrides the default REST resource resolver.
+	 * <p>
+	 * The resource resolver is used to resolve instances from {@link Class} objects defined in the {@link RestResource#children()} annotation.
+	 * The default value is the base class {@link RestResourceResolver}.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#resourceResolver() @RestResource.resourceResolver()} annotation.
+	 *
+	 * @param resourceResolver The new resource resolver.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setResourceResolver(Class<? extends RestResourceResolver> resourceResolver) {
+		this.resourceResolver = resourceResolver;
+		return this;
+	}
+
+	/**
+	 * Overrides the default REST resource resolver.
+	 * <p>
+	 * Same as {@link #setResourceResolver(Class)} except allows you to specify an instance instead of a class.
+	 *
+	 * @param resourceResolver The new resource resolver.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setResourceResolver(RestResourceResolver resourceResolver) {
+		this.resourceResolver = resourceResolver;
+		return this;
+	}
+
+	/**
+	 * Sets the URL path of the resource <js>"/foobar"</js>.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#path() @RestResource.path()} annotation.
+	 *
+	 * @param path The URL path of this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setPath(String path) {
+		if (StringUtils.startsWith(path, '/'))
+			path = path.substring(1);
+		this.path = path;
+		return this;
+	}
+
+	/**
+	 * Sets name of the header used to denote the client version on HTTP requests.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#clientVersionHeader() @RestResource.clientVersionHeader()} annotation.
+	 *
+	 * @param clientVersionHeader The name of the HTTP header that denotes the client version.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setClientVersionHeader(String clientVersionHeader) {
+		this.clientVersionHeader = clientVersionHeader;
+		return this;
+	}
+
+	/**
+	 * Overrides the logger for the resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#logger() @RestResource.logger()} annotation.
+	 *
+	 * @param logger The new logger for this resource.  Can be <jk>null</jk> to disable logging.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setLogger(Class<? extends RestLogger> logger) {
+		this.logger = logger;
+		return this;
+	}
+
+	/**
+	 * Overrides the logger for the resource.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#logger() @RestResource.logger()} annotation.
+	 *
+	 * @param logger The new logger for this resource.  Can be <jk>null</jk> to disable logging.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setLogger(RestLogger logger) {
+		this.logger = logger;
+		return this;
+	}
+
+	/**
+	 * Overrides the call handler for the resource.
+	 * <p>
+	 * The call handler is the object that handles execution of REST HTTP calls.
+	 * Subclasses can be created that customize the behavior of how REST calls are handled.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#callHandler() @RestResource.callHandler()} annotation.
+	 *
+	 * @param restHandler The new call handler for this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setCallHandler(Class<? extends RestCallHandler> restHandler) {
+		this.callHandler = restHandler;
+		return this;
+	}
+
+	/**
+	 * Overrides the call handler for the resource.
+	 * <p>
+	 * The call handler is the object that handles execution of REST HTTP calls.
+	 * Subclasses can be created that customize the behavior of how REST calls are handled.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#callHandler() @RestResource.callHandler()} annotation.
+	 *
+	 * @param restHandler The new call handler for this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setCallHandler(RestCallHandler restHandler) {
+		this.callHandler = restHandler;
+		return this;
+	}
+
+	/**
+	 * Overrides the info provider for the resource.
+	 * <p>
+	 * The info provider provides all the various information about a resource such as the Swagger documentation.
+	 * Subclasses can be created that customize the information.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#infoProvider() @RestResource.infoProvider()} annotation.
+	 *
+	 * @param infoProvider The new info provider for this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setInfoProvider(Class<? extends RestInfoProvider> infoProvider) {
+		this.infoProvider = infoProvider;
+		return this;
+	}
+
+	/**
+	 * Overrides the info provider for the resource.
+	 * <p>
+	 * The info provider provides all the various information about a resource such as the Swagger documentation.
+	 * Subclasses can be created that customize the information.
+	 * <p>
+	 * This is the programmatic equivalent to the {@link RestResource#infoProvider() @RestResource.infoProvider()} annotation.
+	 *
+	 * @param infoProvider The new info provider for this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestConfig setInfoProvider(RestInfoProvider infoProvider) {
+		this.infoProvider = infoProvider;
+		return this;
+	}
+
+	/**
+	 * Creates a new {@link PropertyStore} object initialized with the properties defined in this config.
+	 * @return A new property store.
+	 */
+	protected PropertyStore createPropertyStore() {
+		return PropertyStore.create().addProperties(properties);
+	}
+
+
+	//----------------------------------------------------------------------------------------------------
+	// Methods that give access to the config file, var resolver, and properties.
+	//----------------------------------------------------------------------------------------------------
+
+	/**
+	 * Returns the external configuration file for this resource.
+	 * <p>
+	 * The configuration file location is determined via the {@link RestResource#config() @RestResource.config()} annotation on the resource.
+	 * <p>
+	 * The config file can be programmatically overridden by adding the following method to your resource:
+	 * <p class='bcode'>
+	 * 	<jk>public</jk> ConfigFile createConfigFile(ServletConfig servletConfig) <jk>throws</jk> ServletException;
+	 * </p>
+	 * <p>
+	 * If a config file is not set up, then an empty config file will be returned that is not backed by any file.
+	 *
+	 * @return The external config file for this resource.  Never <jk>null</jk>.
+	 */
+	public ConfigFile getConfigFile() {
+		return configFile;
+	}
+
+	/**
+	 * Returns the configuration properties for this resource.
+	 * <p>
+	 * The configuration properties are determined via the {@link RestResource#properties()} annotation on the resource.
+	 * <p>
+	 * The configuration properties can be augmented programmatically by adding the following method to your resource:
+	 * <p class='bcode'>
+	 * 	<jk>public</jk> ObjectMap createProperties(ServletConfig servletConfig) <jk>throws</jk> ServletException;
+	 * </p>
+	 * <p>
+	 * These properties can be modified during servlet initialization.
+	 * However, any modifications made after {@link RestServlet#init(RestConfig)} has been called will have no effect.
+	 *
+	 * @return The configuration properties for this resource.  Never <jk>null</jk>.
+	 */
+	public ObjectMap getProperties() {
+		return properties;
+	}
+
+	/**
+	 * Creates the variable resolver for this resource.
+	 * <p>
+	 * The variable resolver returned by this method can resolve the following variables:
+	 * <ul>
+	 * 	<li>{@link SystemPropertiesVar}
+	 * 	<li>{@link EnvVariablesVar}
+	 * 	<li>{@link ConfigFileVar}
+	 * 	<li>{@link IfVar}
+	 * 	<li>{@link SwitchVar}
+	 * </ul>
+	 * <p>
+	 * Note that the variables supported here are only a subset of those returned by {@link RestRequest#getVarResolverSession()}.
+	 *
+	 * @return The variable resolver for this resource.  Never <jk>null</jk>.
+	 */
+	public VarResolverBuilder getVarResolverBuilder() {
+		return varResolverBuilder;
+	}
+
+
+	//----------------------------------------------------------------------------------------------------
+	// Methods inherited from ServletConfig
+	//----------------------------------------------------------------------------------------------------
+
+	@Override /* ServletConfig */
+	public String getInitParameter(String name) {
+		return inner.getInitParameter(name);
+	}
+
+	@Override /* ServletConfig */
+	public Enumeration<String> getInitParameterNames() {
+		return inner.getInitParameterNames();
+	}
+
+	@Override /* ServletConfig */
+	public ServletContext getServletContext() {
+		return inner.getServletContext();
+	}
+
+	@Override /* ServletConfig */
+	public String getServletName() {
+		return inner.getServletName();
+	}
+}