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 2018/01/20 18:29:03 UTC

[2/3] juneau git commit: Refactor RestInfoProvider.

http://git-wip-us.apache.org/repos/asf/juneau/blob/62b041ab/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProviderDefault.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProviderDefault.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProviderDefault.java
index 11cad76..983856a 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProviderDefault.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProviderDefault.java
@@ -16,11 +16,13 @@ import static javax.servlet.http.HttpServletResponse.*;
 import static org.apache.juneau.dto.swagger.SwaggerBuilder.*;
 import static org.apache.juneau.internal.ReflectionUtils.*;
 
+import java.lang.reflect.Method;
 import java.util.*;
 import java.util.concurrent.*;
 
 import org.apache.juneau.dto.swagger.*;
 import org.apache.juneau.http.*;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.annotation.*;
@@ -31,8 +33,6 @@ import org.apache.juneau.svl.*;
  * 
  * <p>
  * Subclasses can override these methods to tailor how HTTP REST resources are documented.
- * Subclasses MUST implement a public constructor that takes in a {@link RestContext} object.
- * 
  * 
  * <h5 class='topic'>Additional Information</h5>
  * <ul>
@@ -125,20 +125,17 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Returned objects are cached for later quick-lookup.
 	 * 
 	 * @param req The incoming HTTP request.
-	 * @return The parsed swagger object, or <jk>null</jk> if the swagger file could not be found.
-	 * @throws RestException
+	 * @return The parsed swagger object, or <jk>null</jk> if none could be found.
+	 * @throws Exception 
+	 * 	If swagger file was not valid JSON.
 	 */
 	@Override /* RestInfoProvider */
-	public Swagger getSwaggerFromFile(RestRequest req) throws RestException {
+	public Swagger getSwaggerFromFile(RestRequest req) throws Exception {
 		Locale locale = req.getLocale();
 		Swagger s = swaggers.get(locale);
 		if (s == null) {
-			try {
-				s = context.getClasspathResource(Swagger.class, MediaType.JSON, getClass().getSimpleName() + ".json", locale);
-				swaggers.putIfAbsent(locale, s == null ? Swagger.NULL : s);
-			} catch (Exception e) {
-				throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
-			}
+			s = context.getClasspathResource(Swagger.class, MediaType.JSON, getClass().getSimpleName() + ".json", locale);
+			swaggers.putIfAbsent(locale, s == null ? Swagger.NULL : s);
 		}
 		return s == Swagger.NULL ? null : s;
 	}
@@ -146,98 +143,137 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	/**
 	 * Returns the localized swagger for this REST resource.
 	 * 
+	 * <p>
+	 * Subclasses can override this method to customize the Swagger.
+	 * 
 	 * @param req The incoming HTTP request.
-	 * @return A new Swagger instance.
-	 * @throws RestException
+	 * @return 
+	 * 	A new Swagger instance.
+	 * 	<br>Never <jk>null</jk>.
+	 * @throws Exception
 	 */
 	@Override /* RestInfoProvider */
-	public Swagger getSwagger(RestRequest req) throws RestException {
-		try {
-			// If a file is defined, use that.
-			Swagger s = req.getSwaggerFromFile();
-			if (s != null)
-				return s;
-
-			s = swagger(
-				info(getTitle(req), getVersion(req))
-					.contact(getContact(req))
-					.license(getLicense(req))
-					.description(getDescription(req))
-					.termsOfService(getTermsOfService(req))
-				)
-				.consumes(req.getSupportedAcceptTypes())
-				.produces(req.getSupportedContentTypes())
-				.tags(getTags(req))
-				.externalDocs(getExternalDocs(req));
-
-			for (RestJavaMethod sm : context.getCallMethods().values()) {
-				if (sm.isRequestAllowed(req)) {
-					Operation o = sm.getSwaggerOperation(req);
-					s.path(
-						sm.getPathPattern(),
-						sm.getHttpMethod().toLowerCase(),
-						o
-					);
-				}
-			}
+	public Swagger getSwagger(RestRequest req) throws Exception {
+		
+		// If a file is defined, use that.
+		Swagger s = getSwaggerFromFile(req);
+		if (s != null)
 			return s;
-		} catch (RestException e) {
-			throw e;
-		} catch (Exception e) {
-			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+
+		s = swagger(
+			info(getTitle(req), getVersion(req))
+				.contact(getContact(req))
+				.license(getLicense(req))
+				.description(getDescription(req))
+				.termsOfService(getTermsOfService(req))
+			)
+			.consumes(getConsumes(req))
+			.produces(getProduces(req))
+			.tags(getTags(req))
+			.externalDocs(getExternalDocs(req));
+
+		for (RestJavaMethod sm : context.getCallMethods().values()) {
+			if (sm.isRequestAllowed(req)) {
+				Method m = sm.method;
+				Operation o = operation()
+					.operationId(getMethodOperationId(m, req))
+					.description(getMethodDescription(m, req))
+					.tags(getMethodTags(m, req))
+					.summary(getMethodSummary(m, req))
+					.externalDocs(getMethodExternalDocs(m, req))
+					.parameters(getMethodParameters(m, req))
+					.responses(getMethodResponses(m, req));
+
+				if (isDeprecated(m, req))
+					o.deprecated(true);
+				
+				o.consumes(getMethodConsumes(m, req));
+				o.produces(getMethodProduces(m, req));
+
+				s.path(
+					sm.getPathPattern(),
+					sm.getHttpMethod().toLowerCase(),
+					o
+				);
+			}
 		}
+		
+		return s;
 	}
 
 	/**
-	 * Returns the localized summary of the specified java method on this servlet.
+	 * Returns the localized operation ID of the specified java method.
 	 * 
 	 * <p>
-	 * Subclasses can override this method to provide their own summary.
+	 * Subclasses can override this method to provide their own operation ID.
 	 * 
 	 * <p>
-	 * The default implementation returns the summary from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link RestMethod#summary() @RestMethod.summary()} annotation on the method.
-	 * 	<li><ck>[ClassName].[javaMethodName].summary</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>[javaMethodName].summary</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * </ol>
+	 * The default implementation simply returns the Java method name.
 	 * 
-	 * @param javaMethodName The name of the Java method whose description we're retrieving.
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
 	 * @param req The current request.
-	 * @return The localized summary of the method, or a blank string if no summary was found.
+	 * @return The localized operation ID of the method, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public String getMethodSummary(String javaMethodName, RestRequest req) {
-		RestJavaMethod m = context.getCallMethods().get(javaMethodName);
-		if (m != null)
-			return m.getSummary(req);
-		return "";
+	public String getMethodOperationId(Method method, RestRequest req) throws Exception {
+		return method.getName();
 	}
 
 	/**
-	 * Returns the localized summary of the java method invoked on the specified request.
+	 * Returns the localized summary of the specified java method on this servlet.
 	 * 
 	 * <p>
 	 * Subclasses can override this method to provide their own summary.
 	 * 
 	 * <p>
-	 * The default implementation returns the summary from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link RestMethod#summary() @RestMethod.summary()} annotation on the method.
-	 * 	<li><ck>[ClassName].[javaMethodName].summary</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>[javaMethodName].summary</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link RestMethod#summary() @RestMethod.summary()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ja>@RestMethod</ja>(summary=<js>"Summary of my method"</js>)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ja>@RestMethod</ja>(summary=<js>"$L{myLocalizedSummary}"</js>)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 		</p>
+	 * 	<li>Localized string from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].[javaMethodName].summary</ck>
+	 * 			<li><ck>[javaMethodName].summary</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link RestMethod#summary() @RestMethod.summary()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.myMethod.summary</ck> = <cv>Summary of my method.</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.myMethod.summary</ck> = <cv>$C{MyStrings/MyClass.myMethod.summary}</cv>
+	 * 		</p>
 	 * </ol>
 	 * 
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
 	 * @param req The current request.
-	 * @return The localized summary of the method, or a blank string if no summary was found.
+	 * @return The localized summary of the method, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public String getMethodSummary(RestRequest req) {
-		return getMethodSummary(req.getJavaMethod().getName(), req);
+	public String getMethodSummary(Method method, RestRequest req) throws Exception {
+		VarResolverSession vr = req.getVarResolverSession();
+
+		String s = method.getAnnotation(RestMethod.class).summary();
+		if (s.isEmpty())
+			s = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".summary");
+		if (s != null)
+			return vr.resolve(s);
+		
+		Operation o = getSwaggerOperationFromFile(method, req);
+		return o == null ? null : o.getSummary();
 	}
 
 	/**
@@ -247,49 +283,539 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Subclasses can override this method to provide their own description.
 	 * 
 	 * <p>
-	 * The default implementation returns the description from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link RestMethod#description() @RestMethod.description()} annotation on the method.
-	 * 	<li><ck>[ClassName].[javaMethodName].description</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>[javaMethodName].description</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link RestMethod#description() @RestMethod.description()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ja>@RestMethod</ja>(description=<js>"Description of my method"</js>)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ja>@RestMethod</ja>(description=<js>"$L{myLocalizedDescription}"</js>)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 		</p>
+	 * 	<li>Localized string from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].[javaMethodName].description</ck>
+	 * 			<li><ck>[javaMethodName].description</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link RestMethod#description() @RestMethod.description()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.myMethod.description</ck> = <cv>Description of my method.</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.myMethod.description</ck> = <cv>$C{MyStrings/MyClass.myMethod.description}</cv>
+	 * 		</p>
 	 * </ol>
 	 * 
-	 * @param javaMethodName The name of the Java method whose description we're retrieving.
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
 	 * @param req The current request.
-	 * @return The localized description of the method, or a blank string if no description was found.
+	 * @return The localized description of the method, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public String getMethodDescription(String javaMethodName, RestRequest req) {
-		RestJavaMethod m = context.getCallMethods().get(javaMethodName);
-		if (m != null)
-			return m.getDescription(req);
-		return "";
+	public String getMethodDescription(Method method, RestRequest req) throws Exception {
+		VarResolverSession vr = req.getVarResolverSession();
+		
+		String s = method.getAnnotation(RestMethod.class).description();
+		if (s.isEmpty())
+			s = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".description");
+		if (s != null)
+			return vr.resolve(s);
+		
+		Operation o = getSwaggerOperationFromFile(method, req);
+		return o == null ? null : o.getDescription();
 	}
 
 	/**
-	 * Returns the localized description of the invoked java method on the specified request.
+	 * Returns the localized Swagger tags for this Java method.
 	 * 
 	 * <p>
-	 * Subclasses can override this method to provide their own description.
+	 * Subclasses can override this method to provide their own tags.
+	 * 
+	 * <p>
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link MethodSwagger#tags() @MethodSwagger.tags()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(tags=<js>"foo,bar,baz"</js>)
+	 * 	)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(tags=<js>"$L{myLocalizedTags}"</js>)
+	 * 	)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 		</p>
+	 * 	<li>Localized string from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].[javaMethodName].tags</ck>
+	 * 			<li><ck>[javaMethodName].tags</ck>
+	 * 		</ol>
+	 * 		<br>Value can be a comma-delimited list or JSON array.
+	 * 		<br>Value can contain any SVL variables defined on the {@link MethodSwagger#tags() @MethodSwagger.tags()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Comma-delimited list</cc>
+	 * 	<ck>MyClass.myMethod.tags</ck> = <cv>foo, bar, baz</cv>
+	 * 	
+	 * 	<cc>// JSON array</cc>
+	 * 	<ck>MyClass.myMethod.tags</ck> = <cv>["foo", "bar", "baz"]</cv>
+	 * 
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.myMethod.description</ck> = <cv>$C{MyStrings/MyClass.myMethod.tags}</cv>
+	 * 		</p>
+	 * </ol>
+	 * 
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
+	 * @param req The current request.
+	 * @return The localized tags of the method, or <jk>null</jk> if none were found.
+	 * @throws Exception 
+	 */
+	@Override /* RestInfoProvider */
+	public List<String> getMethodTags(Method method, RestRequest req) throws Exception {
+		JsonParser p = JsonParser.DEFAULT;
+		VarResolverSession vr = req.getVarResolverSession();
+		
+		String s = method.getAnnotation(RestMethod.class).swagger().tags();
+		if (s.isEmpty())
+			s = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".tags");
+		if (s != null) {
+			s = vr.resolve(s);
+			if (StringUtils.isObjectList(s)) 
+				return p.parse(s, ArrayList.class, String.class);
+			return Arrays.asList(StringUtils.split(s));
+		}
+
+		Operation o = getSwaggerOperationFromFile(method, req);
+		return o == null ? null : o.getTags();
+	}
+
+	/**
+	 * Returns the localized external documentation of the specified java method on this servlet.
+	 * 
+	 * <p>
+	 * Subclasses can override this method to provide their own external documentation.
+	 * 
+	 * <p>
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link MethodSwagger#externalDocs() @MethodSwagger.externalDocs()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(externalDocs=<js>"{description:'Find more info here',url:'https://swagger.io'}"</js>)
+	 * 	)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(externalDocs=<js>"$L{myLocalizedExternalDocs}"</js>)
+	 * 	)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 		</p>
+	 * 	<li>Localized string from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].[javaMethodName].externalDocs</ck>
+	 * 			<li><ck>[javaMethodName].externalDocs</ck>
+	 * 		</ol>
+	 * 		<br>Value is a JSON representation of a {@link ExternalDocumentation} object.
+	 * 		<br>Value can contain any SVL variables defined on the {@link MethodSwagger#externalDocs() @MethodSwagger.externalDocs()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.myMethod.externalDocs</ck> = <cv>{description:"Find more info here",url:"https://swagger.io"}</js>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.myMethod.externalDocs</ck> = <cv>$C{MyStrings/MyClass.myMethod.externalDocs}</cv>
+	 * 		</p>
+	 * </ol>
+	 * 
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
+	 * @param req The current request.
+	 * @return The localized external documentation of the method, or <jk>null</jk> if none was found.
+	 * @throws Exception 
+	 */
+	@Override /* RestInfoProvider */
+	public ExternalDocumentation getMethodExternalDocs(Method method, RestRequest req) throws Exception {
+		JsonParser p = JsonParser.DEFAULT;
+		VarResolverSession vr = req.getVarResolverSession();
+		
+		String s = method.getAnnotation(RestMethod.class).swagger().externalDocs();
+		if (s.isEmpty())
+			s = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".externalDocs");
+		if (s != null) 
+			return p.parse(vr.resolve(s), ExternalDocumentation.class);
+
+		Operation o = getSwaggerOperationFromFile(method, req);
+		return o == null ? null : o.getExternalDocs();
+	}
+	
+	/**
+	 * Returns the localized parameter info for the specified java method.
+	 * 
+	 * <p>
+	 * Subclasses can override this method to provide their own parameter info.
+	 * 
+	 * <p>
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>Operation information from swagger file.
+	 * 	<li>{@link MethodSwagger#parameters() @MethodSwagger.parameters()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			parameters={
+	 * 				<ja>@Parameter</ja>(in=<js>"path"</js>, name=<js>"a"</js>, description=<js>"The 'a' attribute"</js>)
+	 * 			}
+	 * 		)
+	 * 	)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			parameters={
+	 * 				<ja>@Parameter</ja>(in=<js>"path"</js>, name=<js>"a"</js>, description=<js>"$L{myLocalizedParamADescription}"</js>)
+	 * 			}
+	 * 		)
+	 * 	)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].[javaMethodName].parameters</ck>
+	 * 			<li><ck>[javaMethodName].parameters</ck>
+	 * 		</ol>
+	 * 		<br>Value is a JSON representation of a <code>{@link ParameterInfo}[]</code> object.
+	 * 		<br>Value can contain any SVL variables defined on the {@link MethodSwagger#parameters() @MethodSwagger.parameters()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.myMethod.parameters</ck> = <cv>[{name:"a",in:"path",description:"The ''a'' attribute"}]</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.myMethod.parameters</ck> = <cv>$C{MyStrings/MyClass.myMethod.parameters}</cv>
+	 * 		</p>
+	 * 	<li>Information gathered directly from the parameters on the Java method.
+	 * </ol>
+	 * 
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
+	 * @param req The current request.
+	 * @return The localized parameter info of the method, or <jk>null</jk> if none was found.
+	 * @throws Exception 
+	 */
+	@Override /* RestInfoProvider */
+	public List<ParameterInfo> getMethodParameters(Method method, RestRequest req) throws Exception {
+		
+		Operation o = getSwaggerOperationFromFile(method, req);
+		if (o != null && o.getParameters() != null)
+			return o.getParameters();
+
+		VarResolverSession vr = req.getVarResolverSession();
+		JsonParser jp = JsonParser.DEFAULT;
+		Map<String,ParameterInfo> m = new TreeMap<>();
+
+		// First parse @RestMethod.parameters() annotation.
+		for (org.apache.juneau.rest.annotation.Parameter v : method.getAnnotation(RestMethod.class).swagger().parameters()) {
+			String in = vr.resolve(v.in());
+			ParameterInfo p = parameterInfo(in, vr.resolve(v.name()));
+
+			if (! v.description().isEmpty())
+				p.description(vr.resolve(v.description()));
+			if (v.required())
+				p.required(v.required());
+
+			if ("body".equals(in)) {
+				if (! v.schema().isEmpty())
+					p.schema(jp.parse(vr.resolve(v.schema()), SchemaInfo.class));
+			} else {
+				if (v.allowEmptyValue())
+					p.allowEmptyValue(v.allowEmptyValue());
+				if (! v.collectionFormat().isEmpty())
+					p.collectionFormat(vr.resolve(v.collectionFormat()));
+				if (! v._default().isEmpty())
+					p._default(vr.resolve(v._default()));
+				if (! v.format().isEmpty())
+					p.format(vr.resolve(v.format()));
+				if (! v.items().isEmpty())
+					p.items(jp.parse(vr.resolve(v.items()), Items.class));
+				p.type(vr.resolve(v.type()));
+			}
+			m.put(p.getIn() + '.' + p.getName(), p);
+		}
+
+		// Next, look in resource bundle.
+		String s = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".parameters");
+		if (s != null) {
+			for (ParameterInfo pi : jp.parse(vr.resolve(s), ParameterInfo[].class)) {
+				String key = pi.getIn() + '.' + pi.getName();
+				ParameterInfo p = m.get(key);
+				if (p == null)
+					m.put(key, pi);
+				else 
+					p.copyFrom(pi);
+			}
+		}
+
+		// Finally, look for parameters defined on method.
+		for (RestParam mp : context.getRestParams(method)) {
+			RestParamType in = mp.getParamType();
+			if (in != RestParamType.OTHER) {
+				String k2 = in.toString() + '.' + (in == RestParamType.BODY ? null : mp.getName());
+				ParameterInfo p = m.get(k2);
+				if (p == null) {
+					p = parameterInfoStrict(in.toString(), mp.getName());
+					m.put(k2, p);
+				}
+			}
+		}
+		
+		return m.isEmpty() ? null : new ArrayList<>(m.values());
+	}
+
+	/**
+	 * Returns the localized response info for the specified java method.
+	 * 
+	 * <p>
+	 * Subclasses can override this method to provide their own parameter info.
+	 * 
+	 * <p>
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>Operation information from swagger file.
+	 * 	<li>{@link MethodSwagger#responses() @MethodSwagger.responses()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			responses={
+	 * 				<ja>@Response</ja>(
+	 * 					value=302,
+	 * 					description=<js>"Thing wasn't found here"</js>,
+	 * 					headers={
+	 * 						<ja>@Parameter</ja>(name=<js>"Location"</js>, description=<js>"The place to find the thing"</js>)
+	 * 					}
+	 * 				)
+	 * 			}
+	 * 		)
+	 * 	)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			responses={
+	 * 				<ja>@Response</ja>(
+	 * 					value=302,
+	 * 					description=<js>"Thing wasn't found here"</js>,
+	 * 					headers={
+	 * 						<ja>@Parameter</ja>(name=<js>"Location"</js>, description=<js>"$L{myLocalizedResponseDescription}"</js>)
+	 * 					}
+	 * 				)
+	 * 			}
+	 * 		)
+	 * 	)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].[javaMethodName].responses</ck>
+	 * 			<li><ck>[javaMethodName].responses</ck>
+	 * 		</ol>
+	 * 		<br>Value is a JSON representation of a <code>Map&lt;Integer,{@link ResponseInfo}&gt;</code> object.
+	 * 		<br>Value can contain any SVL variables defined on the {@link MethodSwagger#responses() @MethodSwagger.responses()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.myMethod.responses</ck> = <cv>{302:{description:'Thing wasn''t found here',headers={Location:{description:"The place to find the thing"}}}</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.myMethod.responses</ck> = <cv>$C{MyStrings/MyClass.myMethod.responses}</cv>
+	 * 		</p>
+	 * 	<li>Information gathered directly from the parameters on the Java method.
+	 * </ol>
+	 * 
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
+	 * @param req The current request.
+	 * @return The localized response info of the method, or <jk>null</jk> if none was found.
+	 * @throws Exception 
+	 */
+	@SuppressWarnings("unchecked")
+	@Override /* RestInfoProvider */
+	public Map<Integer,ResponseInfo> getMethodResponses(Method method, RestRequest req) throws Exception {
+		
+		Operation o = getSwaggerOperationFromFile(method, req);
+		if (o != null && o.getResponses() != null)
+			return o.getResponses();
+
+		VarResolverSession vr = req.getVarResolverSession();
+		JsonParser jp = JsonParser.DEFAULT;
+		Map<Integer,ResponseInfo> m = new TreeMap<>();
+		Map<String,HeaderInfo> m2 = new TreeMap<>();
+
+		// First parse @RestMethod.parameters() annotation.
+		for (Response r : method.getAnnotation(RestMethod.class).swagger().responses()) {
+			int httpCode = r.value();
+			String description = r.description().isEmpty() ? RestUtils.getHttpResponseText(r.value()) : vr.resolve(r.description());
+			ResponseInfo r2 = responseInfo(description);
+
+			if (r.headers().length > 0) {
+				for (org.apache.juneau.rest.annotation.Parameter v : r.headers()) {
+					HeaderInfo h = headerInfoStrict(vr.resolve(v.type()));
+					if (! v.collectionFormat().isEmpty())
+						h.collectionFormat(vr.resolve(v.collectionFormat()));
+					if (! v._default().isEmpty())
+						h._default(vr.resolve(v._default()));
+					if (! v.description().isEmpty())
+						h.description(vr.resolve(v.description()));
+					if (! v.format().isEmpty())
+						h.format(vr.resolve(v.format()));
+					if (! v.items().isEmpty())
+						h.items(jp.parse(vr.resolve(v.items()), Items.class));
+					r2.header(v.name(), h);
+					m2.put(httpCode + '.' + v.name(), h);
+				}
+			}
+			m.put(httpCode, r2);
+		}
+
+		// Next, look in resource bundle.
+		String s = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".responses");
+		if (s != null) {
+			for (Map.Entry<Integer,ResponseInfo> e : ((Map<Integer,ResponseInfo>)jp.parse(vr.resolve(s), Map.class, Integer.class, ResponseInfo.class)).entrySet()) {
+				Integer httpCode = e.getKey();
+				ResponseInfo ri = e.getValue();
+
+				ResponseInfo r = m.get(httpCode);
+				if (r == null)
+					m.put(httpCode, ri);
+				else
+					r.copyFrom(ri);
+			}
+		}
+
+		return m.isEmpty() ? null : m;
+	}
+
+	/**
+	 * Returns the supported <code>Accept</code> types the specified Java method.
 	 * 
 	 * <p>
-	 * The default implementation returns the description from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link RestMethod#description() @RestMethod.description()} annotation on the method.
-	 * 	<li><ck>[ClassName].[javaMethodName].description</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>[javaMethodName].description</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
+	 * Subclasses can override this method to provide their own produces info.
+	 * 
+	 * <p>
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link RestMethod#produces() @RestMethod.supportedAcceptTypes()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ja>@RestMethod</ja>(supportedAcceptTypes={<js>"text/json"</js>})
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ja>@RestMethod</ja>(supportedAcceptTypes={<js>"$C{mySupportedProduces}"</js>})
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 		</p>
+	 * 	<li>Media types defined on the parsers associated with the method.
 	 * </ol>
 	 * 
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
 	 * @param req The current request.
-	 * @return The localized description of the method, or a blank string if no description was found.
+	 * @return The supported <code>Accept</code> types of the method, or <jk>null</jk> if none was found 
+	 * 	or the list of media types match those of the parent resource class.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public String getMethodDescription(RestRequest req) {
-		return getMethodDescription(req.getJavaMethod().getName(), req);
+	public List<MediaType> getMethodProduces(Method method, RestRequest req) throws Exception {
+		VarResolverSession vr = req.getVarResolverSession();
+		String[] s = method.getAnnotation(RestMethod.class).produces();
+		if (s.length > 0)
+			return Arrays.asList(MediaType.forStrings(vr.resolve(s)));
+		List<MediaType> l = req.getSerializers().getSupportedMediaTypes();
+		return (l.equals(context.getProduces())  ? null : l);
+	}
+	
+	/**
+	 * Returns the supported <code>Content-Type</code> types the specified Java method.
+	 * 
+	 * <p>
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link RestMethod#consumes() @RestMethod.supportedContentTypes()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ja>@RestMethod</ja>(supportedContentTypes={<js>"text/json"</js>})
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ja>@RestMethod</ja>(supportedContentTypes={<js>"$C{mySupportedConsumes}"</js>})
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 		</p>
+	 * 	<li>Media types defined on the serializers associated with the method.
+	 * </ol>
+	 * 
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
+	 * @param req The current request.
+	 * @return The supported <code>Content-Type</code> types of the method, or <jk>null</jk> if none was found 
+	 * 	or the list of media types match those of the parent resource class.
+	 * @throws Exception 
+	 */
+	@Override /* RestInfoProvider */
+	public List<MediaType> getMethodConsumes(Method method, RestRequest req) throws Exception {
+		VarResolverSession vr = req.getVarResolverSession();
+		String[] s = method.getAnnotation(RestMethod.class).consumes();
+		if (s.length > 0)
+			return Arrays.asList(MediaType.forStrings(vr.resolve(s)));
+		List<MediaType> l = req.getParsers().getSupportedMediaTypes();
+		return (l.equals(context.getConsumes())  ? null : l);
+	}
+
+	/**
+	 * Returns whether the specified method is deprecated
+	 * 
+	 * <p>
+	 * The default implementation returns the value from the following location:
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link MethodSwagger#deprecated() @MethodSwagger.deprecated()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			deprecated=<jk>true</jk>
+	 * 		)
+	 * 	)
+	 * 	<jk>public</jk> Object myMethod() {...}
+	 * 		</p>
+	 * </ol>
+	 * 
+	 * @param method The Java method annotated with {@link RestMethod @RestMethod}.
+	 * @param req The current request.
+	 * @return <jk>true</jk> if the method is deprecated.
+	 * @throws Exception 
+	 */
+	@Override /* RestInfoProvider */
+	public boolean isDeprecated(Method method, RestRequest req) throws Exception {
+		return method.getAnnotation(RestMethod.class).swagger().deprecated();
 	}
 
 	/**
@@ -299,24 +825,45 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Subclasses can override this method to provide their own site name.
 	 * 
 	 * <p>
-	 * The default implementation returns the description from the following locations (whichever matches first):
-	 * <ol>
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
 	 * 	<li>{@link RestResource#siteName() @RestResource.siteName()} annotation on this class, and then any parent classes.
-	 * 	<li><ck>[ClassName].siteName</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @ResourceBundle.messages()} annotation for this class, then any parent
-	 * 		classes.
-	 * 	<li><ck>siteName</ck> in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
-	 * 		annotation for this class, then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(siteName=<js>"My Site"</js>)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(siteName=<js>"$L{myLocalizedSiteName}"</js>)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].siteName</ck>
+	 * 			<li><ck>siteName</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link RestResource#siteName() @RestResource.siteName()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.siteName</ck> = <cv>My Site</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.siteName</ck> = <cv>$C{MyStrings/MyClass.siteName}</cv>
+	 * 		</p>
 	 * </ol>
 	 * 
 	 * @param req The current request.
-	 * @return The localized description of this REST resource, or <jk>null</jk> if no resource description was found.
+	 * @return The localized site name of this REST resource, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public String getSiteName(RestRequest req) {
+	public String getSiteName(RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
-		if (this.siteName != null)
-			return vr.resolve(this.siteName);
+		if (siteName != null)
+			return vr.resolve(siteName);
 		String siteName = context.getMessages().findFirstString(req.getLocale(), "siteName");
 		if (siteName != null)
 			return vr.resolve(siteName);
@@ -330,29 +877,50 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Subclasses can override this method to provide their own title.
 	 * 
 	 * <p>
-	 * The default implementation returns the description from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link RestResource#title() @RestResource.title()} annotation on this class, and then any parent classes.
-	 * 	<li><ck>[ClassName].title</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @ResourceBundle.messages()} annotation for this class, then any parent
-	 * 		classes.
-	 * 	<li><ck>title</ck> in resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
-	 * 		annotation for this class, then any parent classes.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link RestResource#title() @RestResource.siteName()} annotation on this class, and then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(title=<js>"My Resource"</js>)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(title=<js>"$L{myLocalizedTitle}"</js>)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].title</ck>
+	 * 			<li><ck>title</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link RestResource#title() @RestResource.title()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.title</ck> = <cv>My Resource</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.title</ck> = <cv>$C{MyStrings/MyClass.title}</cv>
+	 * 		</p>
 	 * 	<li><ck>/info/title</ck> entry in swagger file.
 	 * </ol>
 	 * 
 	 * @param req The current request.
-	 * @return The localized description of this REST resource, or <jk>null</jk> if no resource description was found.
+	 * @return The localized title of this REST resource, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public String getTitle(RestRequest req) {
+	public String getTitle(RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
-		if (this.title != null)
-			return vr.resolve(this.title);
+		if (title != null)
+			return vr.resolve(title);
 		String title = context.getMessages().findFirstString(req.getLocale(), "title");
 		if (title != null)
 			return vr.resolve(title);
-		Swagger s = req.getSwaggerFromFile();
+		Swagger s = getSwaggerFromFile(req);
 		if (s != null && s.getInfo() != null)
 			return s.getInfo().getTitle();
 		return null;
@@ -365,29 +933,50 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Subclasses can override this method to provide their own description.
 	 * 
 	 * <p>
-	 * The default implementation returns the description from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link RestResource#description() @RestResource.description()} annotation on this class, and then any
-	 * 		parent classes.
-	 * 	<li><ck>[ClassName].description</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>description</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link RestResource#description() @RestResource.description()} annotation on this class, and then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(description=<js>"My Resource"</js>)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(description=<js>"$L{myLocalizedDescription}"</js>)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].description</ck>
+	 * 			<li><ck>description</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link RestResource#description() @RestResource.description()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.description</ck> = <cv>My Resource</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.description</ck> = <cv>$C{MyStrings/MyClass.description}</cv>
+	 * 		</p>
 	 * 	<li><ck>/info/description</ck> entry in swagger file.
 	 * </ol>
 	 * 
 	 * @param req The current request.
-	 * @return The localized description of this REST resource, or <jk>null</jk> if no resource description was found.
+	 * @return The localized description of this REST resource, or <jk>null</jk> if none was was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public String getDescription(RestRequest req) {
+	public String getDescription(RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
-		if (this.description != null)
-			return vr.resolve(this.description);
+		if (description != null)
+			return vr.resolve(description);
 		String description = context.getMessages().findFirstString(req.getLocale(), "description");
 		if (description != null)
 			return vr.resolve(description);
-		Swagger s = req.getSwaggerFromFile();
+		Swagger s = getSwaggerFromFile(req);
 		if (s != null && s.getInfo() != null)
 			return s.getInfo().getDescription();
 		return null;
@@ -400,32 +989,57 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Subclasses can override this method to provide their own contact information.
 	 * 
 	 * <p>
-	 * The default implementation returns the contact information from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link ResourceSwagger#contact() @ResourceSwagger.contact()} annotation on this class, and then any parent
-	 * 		classes.
-	 * 	<li><ck>[ClassName].contact</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>contact</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link ResourceSwagger#contact() @ResourceSwagger.contact()} annotation on this class, and then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(contact=<js>"{name:'John Smith',email:'john.smith@foo.bar'}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(contact=<js>"$C{MyStrings/MyClass.myContactInfo}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].contact</ck>
+	 * 			<li><ck>contact</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link ResourceSwagger#contact() @ResourceSwagger.contact()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.contact</ck> = <cv>{name:"John Smith",email:"john.smith@foo.bar"}</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.contact</ck> = <cv>$C{MyStrings/MyClass.myContactInfo}</cv>
+	 * 		</p>
 	 * 	<li><ck>/info/contact</ck> entry in swagger file.
 	 * </ol>
 	 * 
 	 * @param req The current request.
 	 * @return
-	 * 	The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+	 * 	The localized contact information of this REST resource, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public Contact getContact(RestRequest req) {
+	public Contact getContact(RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
 		JsonParser jp = JsonParser.DEFAULT;
 		try {
-			if (this.contact != null)
-				return jp.parse(vr.resolve(this.contact), Contact.class);
+			if (contact != null)
+				return jp.parse(vr.resolve(contact), Contact.class);
 			String contact = context.getMessages().findFirstString(req.getLocale(), "contact");
 			if (contact != null)
 				return jp.parse(vr.resolve(contact), Contact.class);
-			Swagger s = req.getSwaggerFromFile();
+			Swagger s = getSwaggerFromFile(req);
 			if (s != null && s.getInfo() != null)
 				return s.getInfo().getContact();
 			return null;
@@ -441,32 +1055,57 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Subclasses can override this method to provide their own license information.
 	 * 
 	 * <p>
-	 * The default implementation returns the license information from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link ResourceSwagger#license() @ResourceSwagger.license()} annotation on this class, and then any parent
-	 * 		classes.
-	 * 	<li><ck>[ClassName].license</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>license</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link ResourceSwagger#license() @ResourceSwagger.license()} annotation on this class, and then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(license=<js>"{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(license=<js>"$C{MyStrings/MyClass.myLicenseInfo}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].license</ck>
+	 * 			<li><ck>license</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link ResourceSwagger#license() @ResourceSwagger.license()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.license</ck> = <cv>{name:"Apache 2.0",url:"http://www.apache.org/licenses/LICENSE-2.0.html"}</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.license</ck> = <cv>$C{MyStrings/MyClass.myLicenseInfo}</cv>
+	 * 		</p>
 	 * 	<li><ck>/info/license</ck> entry in swagger file.
 	 * </ol>
 	 * 
 	 * @param req The current request.
 	 * @return
-	 * 	The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+	 * 	The localized license information of this REST resource, or <jk>null</jk> if none was found found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public License getLicense(RestRequest req) {
+	public License getLicense(RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
 		JsonParser jp = JsonParser.DEFAULT;
 		try {
-			if (this.license != null)
-				return jp.parse(vr.resolve(this.license), License.class);
+			if (license != null)
+				return jp.parse(vr.resolve(license), License.class);
 			String license = context.getMessages().findFirstString(req.getLocale(), "license");
 			if (license != null)
 				return jp.parse(vr.resolve(license), License.class);
-			Swagger s = req.getSwaggerFromFile();
+			Swagger s = getSwaggerFromFile(req);
 			if (s != null && s.getInfo() != null)
 				return s.getInfo().getLicense();
 			return null;
@@ -482,31 +1121,55 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Subclasses can override this method to provide their own terms-of-service information.
 	 * 
 	 * <p>
-	 * The default implementation returns the terms-of-service information from the following locations (whichever
-	 * matches first):
-	 * <ol>
-	 * 	<li>{@link ResourceSwagger#termsOfService() @ResourceSwagger.termsOfService()} annotation on this class, and
-	 * 		then any parent classes.
-	 * 	<li><ck>[ClassName].termsOfService</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>termsOfService</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link ResourceSwagger#termsOfService() @ResourceSwagger.termsOfService()} annotation on this class, and then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(termsOfService=<js>"You're on your own"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(termsOfService=<js>"$C{MyStrings/MyClass.myTermsOfService}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].termsOfService</ck>
+	 * 			<li><ck>termsOfService</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link ResourceSwagger#termsOfService() @ResourceSwagger.termsOfService()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.termsOfService</ck> = <cv>You''re on your own</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.termsOfService</ck> = <cv>$C{MyStrings/MyClass.myTermsOfService}</cv>
+	 * 		</p>
 	 * 	<li><ck>/info/termsOfService</ck> entry in swagger file.
 	 * </ol>
 	 * 
 	 * @param req The current request.
 	 * @return
-	 * 	The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+	 * 	The localized terms-of-service of this REST resource, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public String getTermsOfService(RestRequest req) {
+	public String getTermsOfService(RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
-		if (this.termsOfService != null)
-			return vr.resolve(this.termsOfService);
+		if (termsOfService != null)
+			return vr.resolve(termsOfService);
 		String termsOfService = context.getMessages().findFirstString(req.getLocale(), "termsOfService");
 		if (termsOfService != null)
 			return vr.resolve(termsOfService);
-		Swagger s = req.getSwaggerFromFile();
+		Swagger s = getSwaggerFromFile(req);
 		if (s != null && s.getInfo() != null)
 			return s.getInfo().getTermsOfService();
 		return null;
@@ -519,69 +1182,186 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Subclasses can override this method to provide their own version information.
 	 * 
 	 * <p>
-	 * The default implementation returns the version information from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link ResourceSwagger#version() @ResourceSwagger.version()} annotation on this class, and then any parent
-	 * 		classes.
-	 * 	<li><ck>[ClassName].version</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>version</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link ResourceSwagger#version() @ResourceSwagger.version()} annotation on this class, and then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(version=<js>"2.0"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(version=<js>"$C{MyStrings/MyClass.myVersion}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].version</ck>
+	 * 			<li><ck>version</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link ResourceSwagger#version() @ResourceSwagger.version()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.version</ck> = <cv>2.0</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.version</ck> = <cv>$C{MyStrings/MyClass.myVersion}</cv>
+	 * 		</p>
 	 * 	<li><ck>/info/version</ck> entry in swagger file.
 	 * </ol>
 	 * 
 	 * @param req The current request.
 	 * @return
-	 * 	The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+	 * 	The localized version of this REST resource, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public String getVersion(RestRequest req) {
+	public String getVersion(RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
-		if (this.version != null)
-			return vr.resolve(this.version);
+		if (version != null)
+			return vr.resolve(version);
 		String version = context.getMessages().findFirstString(req.getLocale(), "version");
 		if (version != null)
 			return vr.resolve(version);
-		Swagger s = req.getSwaggerFromFile();
+		Swagger s = getSwaggerFromFile(req);
 		if (s != null && s.getInfo() != null)
 			return s.getInfo().getVersion();
 		return null;
 	}
 
 	/**
+	 * Returns the supported <code>Content-Type</code> request headers for the REST resource.
+	 * <p>
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link ResourceSwagger#version() @ResourceSwagger.version()} annotation on this class, and then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(version=<js>"2.0"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(version=<js>"$C{MyStrings/MyClass.myVersion}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].version</ck>
+	 * 			<li><ck>version</ck>
+	 * 		</ol>
+	 * 		<br>Value can contain any SVL variables defined on the {@link ResourceSwagger#version() @ResourceSwagger.version()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.version</ck> = <cv>2.0</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.version</ck> = <cv>$C{MyStrings/MyClass.myVersion}</cv>
+	 * 		</p>
+	 * 	<li><ck>/info/version</ck> entry in swagger file.
+	 * </ol>
+	 * 
+	 * @param req The current request.
+	 * @return
+	 * 	The supported <code>Content-Type</code> request headers of the REST resource, or <jk>null</jk> if none were found.
+	 * @throws Exception 
+	 */
+	@Override /* RestInfoProvider */
+	public List<MediaType> getConsumes(RestRequest req) throws Exception {
+		List<MediaType> l = req.getContext().getConsumes();
+		return l.isEmpty() ? null : l;
+	}
+	
+	/**
+	 * Returns the supported <code>Accept</code> request headers for the REST resource.
+	 * 
+	 * @param req The current request.
+	 * @return
+	 * 	The supported <code>Accept</code> request headers of the REST resource, or <jk>null</jk> if none were found.
+	 * @throws Exception 
+	 */
+	@Override /* RestInfoProvider */
+	public List<MediaType> getProduces(RestRequest req) throws Exception {
+		List<MediaType> l = req.getContext().getProduces();
+		return l.isEmpty() ? null : l;
+	}
+
+	/**
 	 * Returns the version information of this REST resource.
 	 * 
 	 * <p>
 	 * Subclasses can override this method to provide their own version information.
 	 * 
 	 * <p>
-	 * The default implementation returns the version information from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link ResourceSwagger#version() @ResourceSwagger.version()} annotation on this class, and then any parent
-	 * 		classes.
-	 * 	<li><ck>[ClassName].version</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>version</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>/info/version</ck> entry in swagger file.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link ResourceSwagger#tags() @ResourceSwagger.tags()} annotation on this class, and then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(tags=<js>"foo,bar,baz"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(tags=<js>"$C{MyStrings/MyClass.myTags}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].tags</ck>
+	 * 			<li><ck>tags</ck>
+	 * 		</ol>
+	 * 		<br>Value is either a comma-delimited list or a JSON array.
+	 * 		<br>Value can contain any SVL variables defined on the {@link ResourceSwagger#tags() @ResourceSwagger.tags()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Comma-delimited list</cc>
+	 * 	<ck>MyClass.tags</ck> = <cv>foo,bar,baz</cv>
+	 * 	
+	 * 	<cc>// JSON array</cc>
+	 * 	<ck>MyClass.tags</ck> = <cv>["foo","bar","baz"]</cv>
+	 * 
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.tags</ck> = <cv>$C{MyStrings/MyClass.myTags}</cv>
+	 * 		</p>
+	 * 	<li><ck>tags</ck> entry in swagger file.
 	 * </ol>
 	 * 
 	 * @param req The current request.
 	 * @return
-	 * 	The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+	 * 	The localized tags of this REST resource, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public List<Tag> getTags(RestRequest req) {
+	public List<Tag> getTags(RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
 		JsonParser jp = JsonParser.DEFAULT;
 		try {
-			if (this.tags != null)
-				return jp.parse(vr.resolve(this.tags), ArrayList.class, Tag.class);
+			if (tags != null)
+				return jp.parse(vr.resolve(tags), ArrayList.class, Tag.class);
 			String tags = context.getMessages().findFirstString(req.getLocale(), "tags");
 			if (tags != null)
 				return jp.parse(vr.resolve(tags), ArrayList.class, Tag.class);
-			Swagger s = req.getSwaggerFromFile();
-			if (s != null)
+			Swagger s = getSwaggerFromFile(req);
+			if (s != null && s.getTags() != null)
 				return s.getTags();
 			return null;
 		} catch (Exception e) {
@@ -596,32 +1376,58 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 	 * Subclasses can override this method to provide their own version information.
 	 * 
 	 * <p>
-	 * The default implementation returns the version information from the following locations (whichever matches first):
-	 * <ol>
-	 * 	<li>{@link ResourceSwagger#version() @ResourceSwagger.version()} annotation on this class, and then any parent
-	 * 		classes.
-	 * 	<li><ck>[ClassName].version</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>version</ck> property in resource bundle identified by
-	 * 		{@link RestResource#messages() @RestResource.messages()} annotation for this class, then any parent classes.
-	 * 	<li><ck>/info/version</ck> entry in swagger file.
+	 * The default implementation returns the value from the following locations (whichever matches first):
+	 * <ol class='spaced-list'>
+	 * 	<li>{@link ResourceSwagger#externalDocs() @ResourceSwagger.externalDocs()} annotation on this class, and then any parent classes.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Direct value</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(externalDocs=<js>"{url:'http://juneau.apache.org'}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 	
+	 * 	<jc>// Pulled from some other location</jc>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@ResourceSwagger</ja>(externalDocs=<js>"$C{MyStrings/MyClass.myExternalDocs}"</js>)
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * 		</p>
+	 * 	<li>Localized strings from resource bundle identified by {@link RestResource#messages() @RestResource.messages()}
+	 * 		on the resource class, then any parent classes.
+	 * 		<ol>
+	 * 			<li><ck>[ClassName].externalDocs</ck>
+	 * 			<li><ck>externalDocs</ck>
+	 * 		</ol>
+	 * 		<br>Value is a JSON objec representation of a {@link ExternalDocumentation} object.
+	 * 		<br>Value can contain any SVL variables defined on the {@link ResourceSwagger#externalDocs() @ResourceSwagger.externalDocs()} annotation.
+	 * 		<h6 class='figure'>Examples:</h6>
+	 * 		<p class='bcode'>
+	 * 	<cc>// Direct value</cc>
+	 * 	<ck>MyClass.externalDocs</ck> = <cv>{url:"http://juneau.apache.org"}</cv>
+	 * 	
+	 * 	<cc>// Pulled from some other location</cc>
+	 * 	<ck>MyClass.externalDocs</ck> = <cv>$C{MyStrings/MyClass.myExternalDocs}</cv>
+	 * 		</p>
+	 * 	<li><ck>externalDocs</ck> entry in swagger file.
 	 * </ol>
 	 * 
 	 * @param req The current request.
 	 * @return
-	 * 	The localized contact information of this REST resource, or <jk>null</jk> if no contact information was found.
+	 * 	The localized external documentation of this REST resource, or <jk>null</jk> if none was found.
+	 * @throws Exception 
 	 */
 	@Override /* RestInfoProvider */
-	public ExternalDocumentation getExternalDocs(RestRequest req) {
+	public ExternalDocumentation getExternalDocs(RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
 		JsonParser jp = JsonParser.DEFAULT;
 		try {
-			if (this.externalDocs != null)
-				return jp.parse(vr.resolve(this.externalDocs), ExternalDocumentation.class);
+			if (externalDocs != null)
+				return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
 			String externalDocs = context.getMessages().findFirstString(req.getLocale(), "externalDocs");
 			if (externalDocs != null)
 				return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
-			Swagger s = req.getSwaggerFromFile();
+			Swagger s = getSwaggerFromFile(req);
 			if (s != null)
 				return s.getExternalDocs();
 			return null;
@@ -629,4 +1435,18 @@ public class RestInfoProviderDefault implements RestInfoProvider {
 			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
 		}
 	}
+	
+	private Operation getSwaggerOperationFromFile(Method method, RestRequest req) throws Exception {
+
+		Swagger s = getSwaggerFromFile(req);
+		if (s != null) {
+			Map<String,Map<String,Operation>> sp = s.getPaths();
+			if (sp != null) {
+				Map<String,Operation> spp = sp.get(method.getAnnotation(RestMethod.class).path());
+				if (spp != null)
+					return spp.get(req.getMethod());
+			}
+		}
+		return null;
+	}
 }

http://git-wip-us.apache.org/repos/asf/juneau/blob/62b041ab/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
index b6ef494..b4d0b1d 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
@@ -14,7 +14,6 @@ package org.apache.juneau.rest;
 
 import static javax.servlet.http.HttpServletResponse.*;
 import static org.apache.juneau.BeanContext.*;
-import static org.apache.juneau.dto.swagger.SwaggerBuilder.*;
 import static org.apache.juneau.internal.ClassUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 import static org.apache.juneau.internal.Utils.*;
@@ -27,12 +26,10 @@ import java.util.*;
 import javax.servlet.http.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.dto.swagger.*;
 import org.apache.juneau.encoders.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.internal.*;
-import org.apache.juneau.json.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.widget.*;
@@ -46,17 +43,13 @@ import org.apache.juneau.utils.*;
 public class RestJavaMethod implements Comparable<RestJavaMethod>  {
 	private final String httpMethod;
 	private final UrlPathPattern pathPattern;
-	private final RestParam[] params;
+	final RestParam[] params;
 	private final RestGuard[] guards;
 	private final RestMatcher[] optionalMatchers;
 	private final RestMatcher[] requiredMatchers;
 	private final RestConverter[] converters;
 	private final RestMethodProperties properties;
-	private final boolean deprecated;
-	private final String description, tags, summary, externalDocs;
 	private final Integer priority;
-	private final org.apache.juneau.rest.annotation.Parameter[] parameters;
-	private final Response[] responses;
 	private final RestContext context;
 	final java.lang.reflect.Method method;
 	final SerializerGroup serializers;
@@ -99,21 +92,14 @@ public class RestJavaMethod implements Comparable<RestJavaMethod>  {
 		this.defaultFormData = b.defaultFormData;
 		this.defaultCharset = b.defaultCharset;
 		this.maxInput = b.maxInput;
-		this.deprecated = b.deprecated;
-		this.description = b.description;
-		this.tags = b.tags;
-		this.summary = b.summary;
-		this.externalDocs = b.externalDocs;
 		this.priority = b.priority;
-		this.parameters = b.parameters;
-		this.responses = b.responses;
 		this.supportedAcceptTypes = b.supportedAcceptTypes;
 		this.supportedContentTypes = b.supportedContentTypes;
 		this.widgets = Collections.unmodifiableMap(b.widgets);
 	}
 
 	private static final class Builder  {
-		String httpMethod, defaultCharset, description, tags, summary, externalDocs;
+		String httpMethod, defaultCharset;
 		UrlPathPattern pathPattern;
 		RestParam[] params;
 		RestGuard[] guards;
@@ -127,11 +113,8 @@ public class RestJavaMethod implements Comparable<RestJavaMethod>  {
 		BeanContext beanContext;
 		RestMethodProperties properties;
 		Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
-		boolean deprecated;
 		long maxInput;
 		Integer priority;
-		org.apache.juneau.rest.annotation.Parameter[] parameters;
-		Response[] responses;
 		Map<String,Widget> widgets;
 		List<MediaType> supportedAcceptTypes, supportedContentTypes;
 
@@ -146,18 +129,6 @@ public class RestJavaMethod implements Comparable<RestJavaMethod>  {
 				
 				VarResolver vr = context.getVarResolver();
 
-				if (! m.description().isEmpty())
-					description = m.description();
-				MethodSwagger sm = m.swagger();
-				if (! sm.tags().isEmpty())
-					tags = sm.tags();
-				if (! m.summary().isEmpty())
-					summary = m.summary();
-				if (! sm.externalDocs().isEmpty())
-					externalDocs = sm.externalDocs();
-				deprecated = sm.deprecated();
-				parameters = sm.parameters();
-				responses = sm.responses();
 				serializers = context.getSerializers();
 				parsers = context.getParsers();
 				partSerializer = context.getPartSerializer();
@@ -409,12 +380,12 @@ public class RestJavaMethod implements Comparable<RestJavaMethod>  {
 					beanContext = bcb.build();
 
 				supportedAcceptTypes = 
-					m.supportedAcceptTypes().length > 0 
-					? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(resolveVars(vr, m.supportedAcceptTypes()))))) 
+					m.produces().length > 0 
+					? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(resolveVars(vr, m.produces()))))) 
 					: serializers.getSupportedMediaTypes();
 				supportedContentTypes =
-					m.supportedContentTypes().length > 0 
-					? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(resolveVars(vr, m.supportedContentTypes()))))) 
+					m.consumes().length > 0 
+					? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(resolveVars(vr, m.consumes()))))) 
 					: parsers.getSupportedMediaTypes();
 					
 				params = context.findParams(method, pathPattern, false);
@@ -451,322 +422,6 @@ public class RestJavaMethod implements Comparable<RestJavaMethod>  {
 	}
 
 	/**
-	 * Returns the localized Swagger for this Java method.
-	 */
-	Operation getSwaggerOperation(RestRequest req) throws ParseException {
-		Operation o = operation()
-			.operationId(method.getName())
-			.description(getDescription(req))
-			.tags(getTags(req))
-			.summary(getSummary(req))
-			.externalDocs(getExternalDocs(req))
-			.parameters(getParameters(req))
-			.responses(getResponses(req));
-
-		if (isDeprecated())
-			o.deprecated(true);
-
-		if (! parsers.getSupportedMediaTypes().equals(context.getParsers().getSupportedMediaTypes()))
-			o.consumes(parsers.getSupportedMediaTypes());
-
-		if (! serializers.getSupportedMediaTypes().equals(context.getSerializers().getSupportedMediaTypes()))
-			o.produces(serializers.getSupportedMediaTypes());
-
-		return o;
-	}
-
-	private Operation getSwaggerOperationFromFile(RestRequest req) {
-		Swagger s = req.getSwaggerFromFile();
-		if (s != null && s.getPaths() != null && s.getPaths().get(pathPattern.getPatternString()) != null)
-			return s.getPaths().get(pathPattern.getPatternString()).get(httpMethod);
-		return null;
-	}
-
-	/**
-	 * Returns the localized summary for this Java method.
-	 */
-	String getSummary(RestRequest req) {
-		VarResolverSession vr = req.getVarResolverSession();
-		if (summary != null)
-			return vr.resolve(summary);
-		String summary = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".summary");
-		if (summary != null)
-			return vr.resolve(summary);
-		Operation o = getSwaggerOperationFromFile(req);
-		if (o != null)
-			return o.getSummary();
-		return null;
-	}
-
-	/**
-	 * Returns the localized description for this Java method.
-	 */
-	String getDescription(RestRequest req) {
-		VarResolverSession vr = req.getVarResolverSession();
-		if (description != null)
-			return vr.resolve(description);
-		String description = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".description");
-		if (description != null)
-			return vr.resolve(description);
-		Operation o = getSwaggerOperationFromFile(req);
-		if (o != null)
-			return o.getDescription();
-		return null;
-	}
-
-	/**
-	 * Returns the localized Swagger tags for this Java method.
-	 */
-	private List<String> getTags(RestRequest req) {
-		VarResolverSession vr = req.getVarResolverSession();
-		JsonParser jp = JsonParser.DEFAULT;
-		try {
-			if (tags != null)
-				return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
-			String tags = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".tags");
-			if (tags != null)
-				return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
-			Operation o = getSwaggerOperationFromFile(req);
-			if (o != null)
-				return o.getTags();
-			return null;
-		} catch (Exception e) {
-			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
-		}
-	}
-
-	/**
-	 * Returns the localized Swagger external docs for this Java method.
-	 */
-	private ExternalDocumentation getExternalDocs(RestRequest req) {
-		VarResolverSession vr = req.getVarResolverSession();
-		JsonParser jp = JsonParser.DEFAULT;
-		try {
-			if (externalDocs != null)
-				return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
-			String externalDocs = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".externalDocs");
-			if (externalDocs != null)
-				return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
-			Operation o = getSwaggerOperationFromFile(req);
-			if (o != null)
-				return o.getExternalDocs();
-			return null;
-		} catch (Exception e) {
-			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
-		}
-	}
-
-	/**
-	 * Returns the Swagger deprecated flag for this Java method.
-	 */
-	private boolean isDeprecated() {
-		return deprecated;
-	}
-
-	/**
-	 * Returns the localized Swagger parameter information for this Java method.
-	 */
-	private List<ParameterInfo> getParameters(RestRequest req) throws ParseException {
-		Operation o = getSwaggerOperationFromFile(req);
-		if (o != null && o.getParameters() != null)
-			return o.getParameters();
-
-		VarResolverSession vr = req.getVarResolverSession();
-		JsonParser jp = JsonParser.DEFAULT;
-		Map<String,ParameterInfo> m = new TreeMap<>();
-
-		// First parse @RestMethod.parameters() annotation.
-		for (org.apache.juneau.rest.annotation.Parameter v : parameters) {
-			String in = vr.resolve(v.in());
-			ParameterInfo p = parameterInfo(in, vr.resolve(v.name()));
-
-			if (! v.description().isEmpty())
-				p.description(vr.resolve(v.description()));
-			if (v.required())
-				p.required(v.required());
-
-			if ("body".equals(in)) {
-				if (! v.schema().isEmpty())
-					p.schema(jp.parse(vr.resolve(v.schema()), SchemaInfo.class));
-			} else {
-				if (v.allowEmptyValue())
-					p.allowEmptyValue(v.allowEmptyValue());
-				if (! v.collectionFormat().isEmpty())
-					p.collectionFormat(vr.resolve(v.collectionFormat()));
-				if (! v._default().isEmpty())
-					p._default(vr.resolve(v._default()));
-				if (! v.format().isEmpty())
-					p.format(vr.resolve(v.format()));
-				if (! v.items().isEmpty())
-					p.items(jp.parse(vr.resolve(v.items()), Items.class));
-				p.type(vr.resolve(v.type()));
-			}
-			m.put(p.getIn() + '.' + p.getName(), p);
-		}
-
-		// Next, look in resource bundle.
-		String prefix = method.getName() + ".req";
-		for (String key : context.getMessages().keySet(prefix)) {
-			if (key.length() > prefix.length()) {
-				String value = vr.resolve(context.getMessages().getString(key));
-				String[] parts = key.substring(prefix.length() + 1).split("\\.");
-				String in = parts[0], name, field;
-				boolean isBody = "body".equals(in);
-				if (parts.length == (isBody ? 2 : 3)) {
-					if ("body".equals(in)) {
-						name = null;
-						field = parts[1];
-					} else {
-						name = parts[1];
-						field = parts[2];
-					}
-					String k2 = in + '.' + name;
-					ParameterInfo p = m.get(k2);
-					if (p == null) {
-						p = parameterInfoStrict(in, name);
-						m.put(k2, p);
-					}
-
-					if (field.equals("description"))
-						p.description(value);
-					else if (field.equals("required"))
-						p.required(Boolean.valueOf(value));
-
-					if ("body".equals(in)) {
-						if (field.equals("schema"))
-							p.schema(jp.parse(value, SchemaInfo.class));
-					} else {
-						if (field.equals("allowEmptyValue"))
-							p.allowEmptyValue(Boolean.valueOf(value));
-						else if (field.equals("collectionFormat"))
-							p.collectionFormat(value);
-						else if (field.equals("default"))
-							p._default(value);
-						else if (field.equals("format"))
-							p.format(value);
-						else if (field.equals("items"))
-							p.items(jp.parse(value, Items.class));
-						else if (field.equals("type"))
-							p.type(value);
-					}
-				} else {
-					System.err.println("Unknown bundle key '"+key+"'");
-				}
-			}
-		}
-
-		// Finally, look for parameters defined on method.
-		for (RestParam mp : this.params) {
-			RestParamType in = mp.getParamType();
-			if (in != RestParamType.OTHER) {
-				String k2 = in.toString() + '.' + (in == RestParamType.BODY ? null : mp.getName());
-				ParameterInfo p = m.get(k2);
-				if (p == null) {
-					p = parameterInfoStrict(in.toString(), mp.getName());
-					m.put(k2, p);
-				}
-			}
-		}
-
-		if (m.isEmpty())
-			return null;
-		return new ArrayList<>(m.values());
-	}
-
-	/**
-	 * Returns the localized Swagger response information about this Java method.
-	 */
-	private Map<Integer,ResponseInfo> getResponses(RestRequest req) throws ParseException {
-		Operation o = getSwaggerOperationFromFile(req);
-		if (o != null && o.getResponses() != null)
-			return o.getResponses();
-
-		VarResolverSession vr = req.getVarResolverSession();
-		JsonParser jp = JsonParser.DEFAULT;
-		Map<Integer,ResponseInfo> m = new TreeMap<>();
-		Map<String,HeaderInfo> m2 = new TreeMap<>();
-
-		// First parse @RestMethod.parameters() annotation.
-		for (Response r : responses) {
-			int httpCode = r.value();
-			String description = r.description().isEmpty() ? RestUtils.getHttpResponseText(r.value()) : vr.resolve(r.description());
-			ResponseInfo r2 = responseInfo(description);
-
-			if (r.headers().length > 0) {
-				for (org.apache.juneau.rest.annotation.Parameter v : r.headers()) {
-					HeaderInfo h = headerInfoStrict(vr.resolve(v.type()));
-					if (! v.collectionFormat().isEmpty())
-						h.collectionFormat(vr.resolve(v.collectionFormat()));
-					if (! v._default().isEmpty())
-						h._default(vr.resolve(v._default()));
-					if (! v.description().isEmpty())
-						h.description(vr.resolve(v.description()));
-					if (! v.format().isEmpty())
-						h.format(vr.resolve(v.format()));
-					if (! v.items().isEmpty())
-						h.items(jp.parse(vr.resolve(v.items()), Items.class));
-					r2.header(v.name(), h);
-					m2.put(httpCode + '.' + v.name(), h);
-				}
-			}
-			m.put(httpCode, r2);
-		}
-
-		// Next, look in resource bundle.
-		String prefix = method.getName() + ".res";
-		for (String key : context.getMessages().keySet(prefix)) {
-			if (key.length() > prefix.length()) {
-				String value = vr.resolve(context.getMessages().getString(key));
-				String[] parts = key.substring(prefix.length() + 1).split("\\.");
-				int httpCode = Integer.parseInt(parts[0]);
-				ResponseInfo r2 = m.get(httpCode);
-				if (r2 == null) {
-					r2 = responseInfo(null);
-					m.put(httpCode, r2);
-				}
-
-				String name = parts.length > 1 ? parts[1] : "";
-
-				if ("header".equals(name) && parts.length > 3) {
-					String headerName = parts[2];
-					String field = parts[3];
-
-					String k2 = httpCode + '.' + headerName;
-					HeaderInfo h = m2.get(k2);
-					if (h == null) {
-						h = headerInfoStrict("string");
-						m2.put(k2, h);
-						r2.header(name, h);
-					}
-					if (field.equals("collectionFormat"))
-						h.collectionFormat(value);
-					else if (field.equals("default"))
-						h._default(value);
-					else if (field.equals("description"))
-						h.description(value);
-					else if (field.equals("format"))
-						h.format(value);
-					else if (field.equals("items"))
-						h.items(jp.parse(value, Items.class));
-					else if (field.equals("type"))
-						h.type(value);
-
-				} else if ("description".equals(name)) {
-					r2.description(value);
-				} else if ("schema".equals(name)) {
-					r2.schema(jp.parse(value, SchemaInfo.class));
-				} else if ("examples".equals(name)) {
-					r2.examples(jp.parse(value, TreeMap.class));
-				} else {
-					System.err.println("Unknown bundle key '"+key+"'");
-				}
-			}
-		}
-
-		return m.isEmpty() ? null : m;
-	}
-
-	/**
 	 * Returns <jk>true</jk> if the specified request object can call this method.
 	 */
 	boolean isRequestAllowed(RestRequest req) {
@@ -795,7 +450,7 @@ public class RestJavaMethod implements Comparable<RestJavaMethod>  {
 			remainder = patternVals[pathPattern.getVars().length];
 		for (int i = 0; i < pathPattern.getVars().length; i++)
 			req.getPathMatch().put(pathPattern.getVars()[i], patternVals[i]);
-		req.getPathMatch().setRemainder(remainder);
+		req.getPathMatch().pattern(pathPattern.getPatternString()).remainder(remainder);
 
 		RestRequestProperties requestProperties = new RestRequestProperties(req.getVarResolverSession(), properties);