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/04/21 13:46:25 UTC

[juneau] branch master updated: Improvements to Swagger API

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new e7e7e47  Improvements to Swagger API
e7e7e47 is described below

commit e7e7e475dbab9e053fd55b0a6adbf52fda7399bb
Author: JamesBognar <ja...@apache.org>
AuthorDate: Sat Apr 21 09:45:56 2018 -0400

    Improvements to Swagger API
---
 .../main/java/org/apache/juneau/config/Config.java |   2 +-
 .../org/apache/juneau/utils/StringUtilsTest.java   |  24 +-
 .../java/org/apache/juneau/dto/html5/Select.java   |  19 +
 .../org/apache/juneau/dto/swagger/Swagger.java     |  15 +-
 .../apache/juneau/dto/swagger/ui/SwaggerUI.java    |   2 +-
 .../src/main/java/org/apache/juneau/BeanMap.java   |   2 +-
 .../org/apache/juneau/internal/StringUtils.java    |   4 +-
 .../org/apache/juneau/parser/ParseException.java   |  56 +-
 .../org/apache/juneau/parser/ParserSession.java    |  24 +-
 .../juneau/examples/rest/AtomFeedResource.java     |  16 +-
 .../examples/rest/CodeFormatterResource.java       |  16 +-
 .../examples/rest/DockerRegistryResource.java      |  15 +-
 .../juneau/examples/rest/JsonSchemaResource.java   |  16 +-
 .../examples/rest/MethodExampleResource.java       |  16 +-
 .../juneau/examples/rest/PhotosResource.java       |  16 +-
 .../examples/rest/PredefinedLabelsResource.java    |  16 +-
 .../juneau/examples/rest/RequestEchoResource.java  |  16 +-
 .../examples/rest/SampleRemoteableServlet.java     |  17 +-
 .../juneau/examples/rest/SqlQueryResource.java     |  26 +-
 .../examples/rest/SystemPropertiesResource.java    |  36 +-
 .../juneau/examples/rest/TempDirResource.java      |  16 +-
 .../examples/rest/UrlEncodedFormResource.java      |  16 +-
 .../rest/addressbook/AddressBookResource.java      |  16 +-
 .../{AddPetMenuItem.java => AddOrderMenuItem.java} |  35 +-
 .../examples/rest/petstore/AddPetMenuItem.java     |  13 +-
 .../juneau/examples/rest/petstore/CreateOrder.java |  19 +-
 .../juneau/examples/rest/petstore/Order.java       |  14 +-
 .../apache/juneau/examples/rest/petstore/Pet.java  |   4 +
 .../petstore/{CreatePet.java => PetCreate.java}    |  23 +-
 .../juneau/examples/rest/petstore/PetStore.java    |  50 +-
 .../examples/rest/petstore/PetStoreResource.java   | 309 +++++---
 .../petstore/{CreatePet.java => PetUpdate.java}    |  23 +-
 .../juneau/examples/rest/petstore/Species.java     |   5 +
 .../apache/juneau/examples/rest/petstore/Tag.java  |  12 +
 .../juneau/examples/rest/petstore/Orders.json      |   6 +-
 .../juneau/examples/rest/RootContentTest.java      |   2 +-
 .../microservice/resources/ConfigResource.java     |  70 +-
 .../microservice/resources/LogsResource.java       |  14 +-
 .../org/apache/juneau/rest/test/NlsResource.java   |  26 +-
 .../apache/juneau/rest/test/JacocoDummyTest.java   |   2 +-
 .../org/apache/juneau/rest/test/RestUtilsTest.java |   2 +-
 .../apache/juneau/rest/BasicRestCallHandler.java   |   1 +
 .../apache/juneau/rest/BasicRestInfoProvider.java  | 342 ++++-----
 .../apache/juneau/rest/BasicRestServletGroup.java  |   2 +-
 .../java/org/apache/juneau/rest/RequestBody.java   | 111 +--
 .../java/org/apache/juneau/rest/RestContext.java   |   2 +-
 .../org/apache/juneau/rest/RestContextBuilder.java |  31 +-
 .../org/apache/juneau/rest/RestJavaMethod.java     |   1 +
 .../java/org/apache/juneau/rest/RestRequest.java   |   1 +
 .../java/org/apache/juneau/rest/RestResponse.java  |   1 +
 .../java/org/apache/juneau/rest/RestServlet.java   |   2 +-
 .../juneau/rest/annotation/MethodSwagger.java      | 245 +++++++
 .../juneau/rest/annotation/ResourceSwagger.java    | 356 +++++++++
 .../apache/juneau/rest/annotation/RestMethod.java  |   3 +-
 .../juneau/rest/annotation/RestResource.java       |   3 +-
 .../juneau/rest/remoteable/RemoteableServlet.java  |  14 +-
 .../juneau/rest/response/DefaultHandler.java       |   1 +
 .../rest/util/BoundedServletInputStream.java       | 144 ++++
 .../rest/{ => util}/FinishablePrintWriter.java     |  11 +-
 .../{ => util}/FinishableServletOutputStream.java  |   9 +-
 .../juneau/rest/util/MockHttpServletRequest.java   | 799 +++++++++++++++++++++
 .../juneau/rest/util/MockHttpServletResponse.java  | 240 +++++++
 .../apache/juneau/rest/{ => util}/RestUtils.java   |  15 +-
 63 files changed, 2581 insertions(+), 784 deletions(-)

diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
index 4f3704c..9787f6e 100644
--- a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
@@ -1678,7 +1678,7 @@ public final class Config extends Context implements ConfigEventListener, Writab
 					default: return (T)base64Decode(s);
 				}
 			} catch (Exception e) {
-				throw new ParseException("Value could not be converted to a byte array.").initCause(e);
+				throw new ParseException(e, "Value could not be converted to a byte array.");
 			}
 		}
 		
diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
index 44eaa14..5b0934b 100755
--- a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
@@ -668,24 +668,24 @@ public class StringUtilsTest {
 	public void testParseISO8601Date() throws Exception {
 		WriterSerializer s = JsonSerializer.create().ssq().pojoSwaps(DateSwap.ISO8601DTPZ.class).timeZone(TimeZone.getTimeZone("GMT")).build();
 
-		assertNull(parseISO8601Date(null));
-		assertNull(parseISO8601Date(""));
+		assertNull(parseIsoDate(null));
+		assertNull(parseIsoDate(""));
 
 		TestUtils.setTimeZone("GMT");
 		try {
 
-			assertEquals("'2000-01-01T00:00:00.000Z'", s.serialize(parseISO8601Date("2000")));
-			assertEquals("'2000-02-01T00:00:00.000Z'", s.serialize(parseISO8601Date("2000-02")));
-			assertEquals("'2000-02-03T00:00:00.000Z'", s.serialize(parseISO8601Date("2000-02-03")));
-			assertEquals("'2000-02-03T04:00:00.000Z'", s.serialize(parseISO8601Date("2000-02-03T04")));
-			assertEquals("'2000-02-03T04:05:00.000Z'", s.serialize(parseISO8601Date("2000-02-03T04:05")));
-			assertEquals("'2000-02-03T04:05:06.000Z'", s.serialize(parseISO8601Date("2000-02-03T04:05:06")));
-			assertEquals("'2000-02-03T04:00:00.000Z'", s.serialize(parseISO8601Date("2000-02-03 04")));
-			assertEquals("'2000-02-03T04:05:00.000Z'", s.serialize(parseISO8601Date("2000-02-03 04:05")));
-			assertEquals("'2000-02-03T04:05:06.000Z'", s.serialize(parseISO8601Date("2000-02-03 04:05:06")));
+			assertEquals("'2000-01-01T00:00:00.000Z'", s.serialize(parseIsoDate("2000")));
+			assertEquals("'2000-02-01T00:00:00.000Z'", s.serialize(parseIsoDate("2000-02")));
+			assertEquals("'2000-02-03T00:00:00.000Z'", s.serialize(parseIsoDate("2000-02-03")));
+			assertEquals("'2000-02-03T04:00:00.000Z'", s.serialize(parseIsoDate("2000-02-03T04")));
+			assertEquals("'2000-02-03T04:05:00.000Z'", s.serialize(parseIsoDate("2000-02-03T04:05")));
+			assertEquals("'2000-02-03T04:05:06.000Z'", s.serialize(parseIsoDate("2000-02-03T04:05:06")));
+			assertEquals("'2000-02-03T04:00:00.000Z'", s.serialize(parseIsoDate("2000-02-03 04")));
+			assertEquals("'2000-02-03T04:05:00.000Z'", s.serialize(parseIsoDate("2000-02-03 04:05")));
+			assertEquals("'2000-02-03T04:05:06.000Z'", s.serialize(parseIsoDate("2000-02-03 04:05:06")));
 
 			// ISO8601 doesn't support milliseconds, so it gets trimmed.
-			assertEquals("'2000-02-03T04:05:06.000Z'", s.serialize(parseISO8601Date("2000-02-03 04:05:06,789")));
+			assertEquals("'2000-02-03T04:05:06.000Z'", s.serialize(parseIsoDate("2000-02-03 04:05:06,789")));
 		} finally {
 			TestUtils.unsetTimeZone();
 		}
diff --git a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/html5/Select.java b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/html5/Select.java
index a4c178a..a8595a4 100644
--- a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/html5/Select.java
+++ b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/html5/Select.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.dto.html5;
 
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
 
 /**
  * DTO for an HTML <a class="doclink" href="https://www.w3.org/TR/html5/forms.html#the-select-element">&lt;select&gt;</a>
@@ -134,6 +135,24 @@ public class Select extends HtmlElementContainer {
 		return this;
 	}
 
+	/**
+	 * Convenience method for selecting a child {@link Option} after the options have already been populated. 
+	 * 
+	 * @param optionValue The option value.
+	 * @return This object (for method chaining).
+	 */
+	public Select choose(Object optionValue) {
+		if (optionValue != null) {
+			for (Object o : getChildren()) {
+				if (o instanceof Option) {
+					Option o2 = (Option)o;
+					if (StringUtils.isEquals(optionValue.toString(), o2.getAttr(String.class, "value"))) 
+						o2.selected(true);
+				}
+			}
+		}
+		return this;
+	}
 
 	//--------------------------------------------------------------------------------
 	// Overridden methods
diff --git a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/Swagger.java b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/Swagger.java
index 6bf836d..5f2d824 100644
--- a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/Swagger.java
+++ b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/Swagger.java
@@ -38,6 +38,13 @@ public class Swagger extends SwaggerElement {
 	/** Represents a null swagger */
 	public static final Swagger NULL = new Swagger();
 
+	private static final Comparator<String> PATH_COMPARATOR = new Comparator<String>() {
+		@Override /* Comparator */
+		public int compare(String o1, String o2) {
+			return o1.replace('{', '@').compareTo(o2.replace('{', '@'));
+		}
+	};
+	
 	private String 
 		swagger = "2.0",
 		host, 
@@ -618,7 +625,7 @@ public class Swagger extends SwaggerElement {
 	 * @return This object (for method chaining).
 	 */
 	public Swagger setPaths(Map<String,OperationMap> value) {
-		paths = newSortedMap(value, null);
+		paths = newSortedMap(value, PATH_COMPARATOR);
 		return this;
 	}
 
@@ -637,7 +644,7 @@ public class Swagger extends SwaggerElement {
 	 * @return This object (for method chaining).
 	 */
 	public Swagger addPaths(Map<String,OperationMap> values) {
-		paths = addToSortedMap(paths, values, null);
+		paths = addToSortedMap(paths, values, PATH_COMPARATOR);
 		return this;
 	}
 	
@@ -651,7 +658,7 @@ public class Swagger extends SwaggerElement {
 	 */
 	public Swagger path(String path, String methodName, Operation operation) {
 		if (paths == null)
-			paths = new TreeMap<>();
+			paths = new TreeMap<>(PATH_COMPARATOR);
 		OperationMap p = paths.get(path);
 		if (p == null) {
 			p = new OperationMap();
@@ -681,7 +688,7 @@ public class Swagger extends SwaggerElement {
 	@SuppressWarnings({ "unchecked", "rawtypes" })
 	public Swagger paths(Object...values) {
 		if (paths == null)
-			paths = new TreeMap<>();
+			paths = new TreeMap<>(PATH_COMPARATOR);
 		paths = addToMap((Map)paths, values, String.class, Map.class, String.class, Operation.class);
 		return this;
 	}
diff --git a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/ui/SwaggerUI.java b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/ui/SwaggerUI.java
index 945e306..6e8f063 100644
--- a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/ui/SwaggerUI.java
+++ b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/ui/SwaggerUI.java
@@ -201,7 +201,7 @@ public class SwaggerUI extends PojoSwap<Swagger,Div> {
 	private Div opBlock(Session s, String path, String opName, Operation op) {
 		
 		String opClass = op.isDeprecated() ? "deprecated" : opName.toLowerCase();
-		if (! STANDARD_METHODS.contains(opClass))
+		if (! op.isDeprecated() && ! STANDARD_METHODS.contains(opClass))
 			opClass = "other";
 		
 		return div()._class("op-block op-block-closed " + opClass).children(
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
index 810effd..bf89ba0 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
@@ -168,7 +168,7 @@ public class BeanMap<T> extends AbstractMap<String,Object> implements Delegate<T
 					put(e.getKey(), e.getValue());
 				propertyCache = null;
 			} catch (IllegalArgumentException e) {
-				throw new BeanRuntimeException("IllegalArgumentException occurred on call to class constructor ''{0}'' with argument types ''{1}''", c.getName(), JsonSerializer.DEFAULT_LAX.toString(ClassUtils.getClasses(args)));
+				throw new BeanRuntimeException(e, meta.classMeta.innerClass, "IllegalArgumentException occurred on call to class constructor ''{0}'' with argument types ''{1}''", c.getName(), JsonSerializer.DEFAULT_LAX.toString(ClassUtils.getClasses(args)));
 			} catch (Exception e) {
 				throw new BeanRuntimeException(e);
 			}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
index 8805c10..14153b4 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
@@ -204,7 +204,7 @@ public final class StringUtils {
 				return new AtomicInteger(Integer.decode(s));
 			throw new ParseException("Unsupported Number type: {0}", type.getName());
 		} catch (NumberFormatException e) {
-			throw new ParseException("Invalid number: ''{0}'', class=''{1}''", s, type.getSimpleName()).initCause(e);
+			throw new ParseException(e, "Invalid number: ''{0}'', class=''{1}''", s, type.getSimpleName());
 		}
 	}
 
@@ -1140,7 +1140,7 @@ public final class StringUtils {
 	 * @return The parsed date.
 	 * @throws IllegalArgumentException
 	 */
-	public static Date parseISO8601Date(String date) throws IllegalArgumentException {
+	public static Date parseIsoDate(String date) throws IllegalArgumentException {
 		if (isEmpty(date))
 			return null;
 		date = date.trim().replace(' ', 'T');  // Convert to 'standard' ISO8601
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParseException.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParseException.java
index 4acc7bb..56a12cf 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParseException.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParseException.java
@@ -30,6 +30,36 @@ public class ParseException extends FormattedException {
 	/**
 	 * Constructor.
 	 * 
+	 * @param message The {@link MessageFormat}-style message.
+	 * @param args Optional {@link MessageFormat}-style arguments.
+	 */
+	public ParseException(String message, Object...args) {
+		super(message, args);
+	}
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param causedBy The cause of this exception.
+	 * @param message The {@link MessageFormat}-style message.
+	 * @param args Optional {@link MessageFormat}-style arguments.
+	 */
+	public ParseException(Throwable causedBy, String message, Object...args) {
+		super(causedBy, message, args);
+	}
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param causedBy The cause of this exception.
+	 */
+	public ParseException(Throwable causedBy) {
+		super(causedBy);
+	}
+
+	/**
+	 * Constructor.
+	 * 
 	 * @param session The parser session.
 	 * @param message The exception message containing {@link MessageFormat}-style arguments.
 	 * @param args Optional {@link MessageFormat}-style arguments.
@@ -41,11 +71,13 @@ public class ParseException extends FormattedException {
 	/**
 	 * Constructor.
 	 * 
+	 * @param session The parser session.
+	 * @param causedBy The cause of this exception.
 	 * @param message The exception message containing {@link MessageFormat}-style arguments.
 	 * @param args Optional {@link MessageFormat}-style arguments.
 	 */
-	public ParseException(String message, Object...args) {
-		this(null, message, args);
+	public ParseException(ParserSession session, Throwable causedBy, String message, Object...args) {
+		super(causedBy, getMessage(session, message, args));
 	}
 
 	/**
@@ -58,14 +90,6 @@ public class ParseException extends FormattedException {
 		super(causedBy, getMessage(session, causedBy.getMessage()));
 	}
 
-	/**
-	 * Constructor.
-	 * 
-	 * @param causedBy The inner exception.
-	 */
-	public ParseException(Exception causedBy) {
-		super(causedBy, getMessage(null, causedBy.getMessage()));
-	}
 
 	private static String getMessage(ParserSession session, String msg, Object... args) {
 		if (args.length != 0)
@@ -109,16 +133,4 @@ public class ParseException extends FormattedException {
 			t = (ParseException)t.getCause();
 		return t;
 	}
-
-	/**
-	 * Sets the inner cause for this exception.
-	 * 
-	 * @param cause The inner cause.
-	 * @return This object (for method chaining).
-	 */
-	@Override /* Throwable */
-	public synchronized ParseException initCause(Throwable cause) {
-		super.initCause(cause);
-		return this;
-	}
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
index ff4e2a1..b03477e 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
@@ -513,11 +513,11 @@ public abstract class ParserSession extends BeanSession {
 		} catch (StackOverflowError e) {
 			throw new ParseException(this, "Depth too deep.  Stack overflow occurred.");
 		} catch (IOException e) {
-			throw new ParseException(this, "I/O exception occurred.  exception={0}, message={1}.",
-				e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e);
+			throw new ParseException(this, e, "I/O exception occurred.  exception={0}, message={1}.",
+				e.getClass().getSimpleName(), e.getLocalizedMessage());
 		} catch (Exception e) {
-			throw new ParseException(this, "Exception occurred.  exception={0}, message={1}.",
-				e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e);
+			throw new ParseException(this, e, "Exception occurred.  exception={0}, message={1}.",
+				e.getClass().getSimpleName(), e.getLocalizedMessage());
 		} finally {
 			checkForWarnings();
 		}
@@ -604,11 +604,11 @@ public abstract class ParserSession extends BeanSession {
 		} catch (StackOverflowError e) {
 			throw new ParseException(this, "Depth too deep.  Stack overflow occurred.");
 		} catch (IOException e) {
-			throw new ParseException(this, "I/O exception occurred.  exception={0}, message={1}.",
-				e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e);
+			throw new ParseException(this, e, "I/O exception occurred.  exception={0}, message={1}.",
+				e.getClass().getSimpleName(), e.getLocalizedMessage());
 		} catch (Exception e) {
-			throw new ParseException(this, "Exception occurred.  exception={0}, message={1}.",
-				e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e);
+			throw new ParseException(this, e, "Exception occurred.  exception={0}, message={1}.",
+				e.getClass().getSimpleName(), e.getLocalizedMessage());
 		} finally {
 			checkForWarnings();
 		}
@@ -659,11 +659,11 @@ public abstract class ParserSession extends BeanSession {
 		} catch (StackOverflowError e) {
 			throw new ParseException(this, "Depth too deep.  Stack overflow occurred.");
 		} catch (IOException e) {
-			throw new ParseException(this, "I/O exception occurred.  exception={0}, message={1}.",
-				e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e);
+			throw new ParseException(this, e, "I/O exception occurred.  exception={0}, message={1}.",
+				e.getClass().getSimpleName(), e.getLocalizedMessage());
 		} catch (Exception e) {
-			throw new ParseException(this, "Exception occurred.  exception={0}, message={1}.",
-				e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e);
+			throw new ParseException(this, e, "Exception occurred.  exception={0}, message={1}.",
+				e.getClass().getSimpleName(), e.getLocalizedMessage());
 		} finally {
 			checkForWarnings();
 		}
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/AtomFeedResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/AtomFeedResource.java
index f91c4a3..424991c 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/AtomFeedResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/AtomFeedResource.java
@@ -52,15 +52,13 @@ import org.apache.juneau.rest.widget.*;
 		@Property(name=BEAN_examples, value="{'org.apache.juneau.dto.atom.Feed': $F{AtomFeedResource_example.json}}")
 	},
 	encoders=GzipEncoder.class,
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}	
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class AtomFeedResource extends BasicRestServletJena {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/CodeFormatterResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/CodeFormatterResource.java
index cb44fbe..82fe991 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/CodeFormatterResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/CodeFormatterResource.java
@@ -42,15 +42,13 @@ import org.apache.juneau.rest.annotation.*;
 		},
 		style="aside {display:table-caption;}"
 	),
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 @SuppressWarnings({"serial"})
 public class CodeFormatterResource extends BasicRestServlet {
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DockerRegistryResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DockerRegistryResource.java
index a8e5461..90f8627 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DockerRegistryResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DockerRegistryResource.java
@@ -39,15 +39,12 @@ import org.apache.juneau.rest.helper.*;
 		// Pull in aside contents from file.
 		aside="$F{resources/DockerRegistryResourceAside.html}"
 	),
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class DockerRegistryResource extends BasicRestServlet {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/JsonSchemaResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/JsonSchemaResource.java
index 07839df..0dc53a2 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/JsonSchemaResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/JsonSchemaResource.java
@@ -49,15 +49,13 @@ import org.apache.juneau.rest.widget.*;
 			"</div>"
 		}
 	),
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'}"
+	)
 )
 public class JsonSchemaResource extends BasicRestServletJena {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/MethodExampleResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/MethodExampleResource.java
index b9f6e4d..376e924 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/MethodExampleResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/MethodExampleResource.java
@@ -41,15 +41,13 @@ import org.apache.juneau.utils.*;
 			"</div>"
 		}
 	),
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class MethodExampleResource extends BasicRestServlet {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PhotosResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PhotosResource.java
index 6c6bc3e..58b576a 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PhotosResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PhotosResource.java
@@ -54,15 +54,13 @@ import org.apache.juneau.serializer.*;
 		// Make the anchor text on URLs be just the path relative to the servlet.
 		@Property(name=HTML_uriAnchorText, value="SERVLET_RELATIVE")
 	},
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class PhotosResource extends BasicRestServlet {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PredefinedLabelsResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PredefinedLabelsResource.java
index da0befb..630a978 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PredefinedLabelsResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PredefinedLabelsResource.java
@@ -41,15 +41,13 @@ import org.apache.juneau.rest.widget.*;
 			"source: $C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
 		}
 	),
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class PredefinedLabelsResource extends BasicRestServlet {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java
index 800fa13..f94cb96 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java
@@ -63,15 +63,13 @@ import org.apache.juneau.transforms.*;
 		// Add a special filter for Enumerations
 		EnumerationSwap.class
 	},
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class RequestEchoResource extends BasicRestServlet {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SampleRemoteableServlet.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SampleRemoteableServlet.java
index c32de94..6bcf1ea 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SampleRemoteableServlet.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SampleRemoteableServlet.java
@@ -37,16 +37,13 @@ import org.apache.juneau.rest.remoteable.*;
 	),
 	// Allow us to use method=POST from a browser.
 	allowedMethodParams="*",
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
-
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class SampleRemoteableServlet extends RemoteableServlet {
 
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SqlQueryResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SqlQueryResource.java
index 78e6e21..8216770 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SqlQueryResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SqlQueryResource.java
@@ -54,15 +54,13 @@ import org.apache.juneau.rest.widget.*;
 			"</div>"
 		}
 	),
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class SqlQueryResource extends BasicRestServlet {
 	private static final long serialVersionUID = 1L;
@@ -146,11 +144,11 @@ public class SqlQueryResource extends BasicRestServlet {
 		name=POST, 
 		path="/", 
 		summary="Execute one or more queries",
-		swagger= {
-			"responses:{",
-				"200:{ description:'Query results.\nEach entry in the array is a result of one query.\nEach result can be a result set (for queries) or update count (for updates).', 'x-example':[[{col1:'val1'},{col2:'val2'},{col3:'val3'}],123]}",
-			"}",
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200:{ description:'Query results.\nEach entry in the array is a result of one query.\nEach result can be a result set (for queries) or update count (for updates).', 'x-example':[[{col1:'val1'},{col2:'val2'},{col3:'val3'}],123]}"
+			}
+		)
 	)
 	public List<Object> doPost(
 			@Body(description="Query input", example="{sql:'select * from sys.systables',pos:1,limit:100}") PostInput in
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SystemPropertiesResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SystemPropertiesResource.java
index 369057b..dc1faf2 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SystemPropertiesResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SystemPropertiesResource.java
@@ -81,15 +81,13 @@ import org.apache.juneau.rest.widget.*;
 	// Support GZIP encoding on Accept-Encoding header.
 	encoders=GzipEncoder.class,
 
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class SystemPropertiesResource extends BasicRestServlet {
 	private static final long serialVersionUID = 1L;
@@ -98,11 +96,11 @@ public class SystemPropertiesResource extends BasicRestServlet {
 		name=GET, path="/",
 		summary="Show all system properties",
 		description="Returns all system properties defined in the JVM.",
-		swagger={
-			"responses:{",
-				"200: {description:'Returns a map of key/value pairs.', x-example:{key1:'val1',key2:'val2'}}",
-			"}"
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200: {description:'Returns a map of key/value pairs.', x-example:{key1:'val1',key2:'val2'}}"
+			}
+		)
 	)
 	@SuppressWarnings({"rawtypes", "unchecked"})
 	public Map getSystemProperties(
@@ -118,11 +116,11 @@ public class SystemPropertiesResource extends BasicRestServlet {
 		name=GET, path="/{propertyName}",
 		summary="Get system property",
 		description="Returns the value of the specified system property.",
-		swagger={
-			"responses:{",
-				"200: {description:'The system property value, or null if not found'}",
-			"}"
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200: {description:'The system property value, or null if not found'}"
+			}
+		)
 	)
 	public String getSystemProperty(
 			@Path(description="The system property name.", example="PATH") String propertyName
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/TempDirResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/TempDirResource.java
index baa58a8..58d28c6 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/TempDirResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/TempDirResource.java
@@ -63,15 +63,13 @@ import org.apache.juneau.utils.*;
 		@Property(name=DIRECTORY_RESOURCE_allowDeletes, value="true"),
 		@Property(name=DIRECTORY_RESOURCE_allowUploads, value="false")
 	},
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class TempDirResource extends DirectoryResource {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/UrlEncodedFormResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/UrlEncodedFormResource.java
index 2da113e..4c7edbc 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/UrlEncodedFormResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/UrlEncodedFormResource.java
@@ -48,15 +48,13 @@ import org.apache.juneau.transforms.*;
 			"</div>"
 		}
 	),
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class UrlEncodedFormResource extends BasicRestServlet {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
index 3370f5c..14f4637 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
@@ -115,15 +115,13 @@ import org.apache.juneau.utils.*;
 	encoders=GzipEncoder.class,
 
 	// Swagger info.
-	swagger={
-		"info: {",
-			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
-			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-			"version:'2.0',",
-			"termsOfService:'You are on your own.'",
-		"},",
-		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
-	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'"
+	)
 )
 public class AddressBookResource extends BasicRestServletJena {
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddOrderMenuItem.java
similarity index 75%
copy from juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java
copy to juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddOrderMenuItem.java
index a493027..458c6e3 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddOrderMenuItem.java
@@ -15,42 +15,49 @@ package org.apache.juneau.examples.rest.petstore;
 import static org.apache.juneau.dto.html5.HtmlBuilder.*;
 import static org.apache.juneau.http.HttpMethodName.*;
 
+import java.util.*;
+import java.util.Map;
+
+import org.apache.juneau.dto.html5.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.widget.*;
 
 /**
  * Menu item for adding a Pet.
  */
-public class AddPetMenuItem extends MenuItemWidget {
+public class AddOrderMenuItem extends MenuItemWidget {
 
 	@Override /* MenuItemWidget */
 	public String getLabel(RestRequest req) throws Exception {
 		return "add";
 	}
 
+	@SuppressWarnings("unchecked")
 	@Override /* Widget */
 	public Object getContent(RestRequest req) throws Exception {
+		Map<Long,String> petNames = (Map<Long,String>)req.getAttribute("availablePets");
+	
+		List<Option> options = new ArrayList<>();
+		for (Map.Entry<Long,String> e : petNames.entrySet())
+			options.add(option(e.getKey(), e.getValue()));
+	
+		
 		return div(
-			form().id("form").action("servlet:/").method(POST).children(
+			form().id("form").action("servlet:/store/order").method(POST).children(
 				table(
 					tr(
-						th("Name:"),
-						td(input().name("name").type("text")),
-						td(new Tooltip("(?)", "The name of the pet.", br(), "e.g. 'Fluffy'")) 
-					),
-					tr(
-						th("Species:"),
+						th("Pet:"),
 						td(
-							select().name("kind").children(
-								option("CAT"), option("DOG"), option("BIRD"), option("FISH"), option("MOUSE"), option("RABBIT"), option("SNAKE")
+							select().name("petId").children(
+								options.toArray()
 							)
 						),
-						td(new Tooltip("(?)", "The kind of animal.")) 
+						td(new Tooltip("(?)", "The pet to purchase.")) 
 					),
 					tr(
-						th("Price:"),
-						td(input().name("price").type("number").placeholder("1.0").step("0.01").min(1).max(100)),
-						td(new Tooltip("(?)", "The price to charge for this pet.")) 
+						th("Ship date:"),
+						td(input().name("shipDate").type("date")),
+						td(new Tooltip("(?)", "The requested ship date.")) 
 					),
 					tr(
 						td().colspan(2).style("text-align:right").children(
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java
index a493027..985c194 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/AddPetMenuItem.java
@@ -31,7 +31,7 @@ public class AddPetMenuItem extends MenuItemWidget {
 	@Override /* Widget */
 	public Object getContent(RestRequest req) throws Exception {
 		return div(
-			form().id("form").action("servlet:/").method(POST).children(
+			form().id("form").action("servlet:/pet").method(POST).children(
 				table(
 					tr(
 						th("Name:"),
@@ -41,18 +41,23 @@ public class AddPetMenuItem extends MenuItemWidget {
 					tr(
 						th("Species:"),
 						td(
-							select().name("kind").children(
-								option("CAT"), option("DOG"), option("BIRD"), option("FISH"), option("MOUSE"), option("RABBIT"), option("SNAKE")
+							select().name("species").children(
+								option("cat"), option("dog"), option("bird"), option("fish"), option("mouse"), option("rabbit"), option("snake")
 							)
 						),
 						td(new Tooltip("(?)", "The kind of animal.")) 
 					),
 					tr(
 						th("Price:"),
-						td(input().name("price").type("number").placeholder("1.0").step("0.01").min(1).max(100)),
+						td(input().name("price").type("number").placeholder("1.0").step("0.01").min(1).max(100).value(9.99)),
 						td(new Tooltip("(?)", "The price to charge for this pet.")) 
 					),
 					tr(
+						th("Tags:"),
+						td(input().name("tags").type("text")),
+						td(new Tooltip("(?)", "Arbitrary textual tags (comma-delimited).", br(), "e.g. 'fluffy,friendly'")) 
+					),
+					tr(
 						td().colspan(2).style("text-align:right").children(
 							button("reset", "Reset"),
 							button("button","Cancel").onclick("window.location.href='/'"),
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreateOrder.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreateOrder.java
index 5bc4bca..9f62861 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreateOrder.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreateOrder.java
@@ -12,36 +12,33 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.examples.rest.petstore;
 
+import java.util.*;
+
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
 
 /**
  * Bean for creating {@link Order} objects.
  */
 public class CreateOrder {
 	private final long petId;
-	private final int quantity;
-	private final String shipDate;
+	private final Date shipDate;
 
-	@BeanConstructor(properties="petId,quantity,shipDate")
-	public CreateOrder(long petId, int quantity, String shipDate) {
+	@BeanConstructor(properties="petId,shipDate")
+	public CreateOrder(long petId, Date shipDate) {
 		this.petId = petId;
-		this.quantity = quantity;
 		this.shipDate = shipDate;
 	}
 	
 	public static CreateOrder example() {
-		return new CreateOrder(123, 10, "2012-12-21");
+		return new CreateOrder(123, StringUtils.parseIsoDate("2012-12-21"));
 	}
 
 	public long getPetId() {
 		return petId;
 	}
 
-	public int getQuantity() {
-		return quantity;
-	}
-
-	public String getShipDate() {
+	public Date getShipDate() {
 		return shipDate;
 	}
 }
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Order.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Order.java
index 9886fc1..73dc1cf 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Order.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Order.java
@@ -18,11 +18,10 @@ import org.apache.juneau.annotation.*;
 import org.apache.juneau.html.annotation.*;
 import org.apache.juneau.transforms.*;
 
-@Bean(fluentSetters=true, properties="id,petId,quantity,shipDate,status")
-@Example("{id:123,petId:456,quantity:100,shipDate:'2012-12-21',status:'APPROVED'}")
+@Bean(fluentSetters=true, properties="id,petId,shipDate,status")
+@Example("{id:123,petId:456,shipDate:'2012-12-21',status:'APPROVED'}")
 public class Order {
 	private long id, petId;
-	private int quantity;
 	private Date shipDate;
 	private OrderStatus status;
 	
@@ -46,15 +45,6 @@ public class Order {
 		return this;
 	}
 	
-	public int getQuantity() {
-		return quantity;
-	}
-	
-	public Order quantity(int quantity) {
-		this.quantity = quantity;
-		return this;
-	}
-	
 	@Swap(DateSwap.ISO8601D.class)
 	public Date getShipDate() {
 		return shipDate;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Pet.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Pet.java
index 240a2a7..7b20f89 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Pet.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Pet.java
@@ -128,4 +128,8 @@ public class Pet {
 		this.price = price;
 		return this;
 	}
+
+	public java.net.URI getEdit() {
+		return java.net.URI.create("servlet:/pet/edit/{id}");
+	}
 }
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreatePet.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetCreate.java
similarity index 78%
copy from juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreatePet.java
copy to juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetCreate.java
index 2e91036..3dcaa0b 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreatePet.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetCreate.java
@@ -12,33 +12,28 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.examples.rest.petstore;
 
-import java.util.*;
-
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.utils.*;
 
 /**
  * Bean for creating {@link Pet} objects.
  */
-public class CreatePet {
+public class PetCreate {
 
 	private final String name;
 	private final float price;
 	private final String species;
-	private final List<String> tags;
-	private final PetStatus status;
+	private final String[] tags;
 	
-	@BeanConstructor(properties="name,price,species,tags,status")
-	public CreatePet(String name, float price, String species, List<String> tags, PetStatus status) {
+	@BeanConstructor(properties="name,price,species,tags")
+	public PetCreate(String name, float price, String species, String[] tags) {
 		this.name = name;
 		this.price = price;
 		this.species = species;
 		this.tags = tags;
-		this.status = status;
 	}
 	
-	public static CreatePet example() {
-		return new CreatePet("Doggie", 9.99f, "doc", AList.create("friendly","cute"), PetStatus.AVAILABLE);
+	public static PetCreate example() {
+		return new PetCreate("Doggie", 9.99f, "doc", new String[] {"friendly","cute"});
 	}
 
 	public String getName() {
@@ -53,11 +48,7 @@ public class CreatePet {
 		return species;
 	}
 
-	public List<String> getTags() {
+	public String[] getTags() {
 		return tags;
 	}
-
-	public PetStatus getStatus() {
-		return status;
-	}
 }
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStore.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStore.java
index 7c786f2..20866f1 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStore.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStore.java
@@ -17,7 +17,6 @@ import java.util.*;
 import java.util.concurrent.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.transform.*;
 import org.apache.juneau.utils.*;
@@ -187,29 +186,47 @@ public class PetStore {
 		return value;
 	}
 	
-	public Pet create(CreatePet c) {
+	public Pet create(PetCreate pc) {
 		Pet p = new Pet();
-		p.name(c.getName());
-		p.price(c.getPrice());
-		p.species(getSpecies(c.getName()));
-		p.tags(getTags(c.getTags()));
-		p.status(c.getStatus());
+		p.name(pc.getName());
+		p.price(pc.getPrice());
+		p.species(getSpecies(pc.getSpecies()));
+		p.tags(getTags(pc.getTags()));
+		p.status(PetStatus.AVAILABLE);
 		return add(p);
 	}
 
+	public Pet update(PetUpdate pu) throws IdNotFound {
+		Pet p = petDb.get(pu.getId());
+		if (p == null)
+			throw new IdNotFound(pu.getId(), Pet.class);
+		p.name(pu.getName());
+		p.price(pu.getPrice());
+		p.species(getSpecies(pu.getSpecies()));
+		p.tags(getTags(pu.getTags()));
+		p.status(pu.getStatus());
+		return p;
+	}
+
+	public Pet update(Pet pet) {
+		petDb.put(pet.getId(), pet);
+		return pet;
+	}
+
 	public Order create(CreateOrder c) {
 		Order o = new Order();
 		o.petId(c.getPetId());
-		o.quantity(c.getQuantity());
-		o.shipDate(StringUtils.parseISO8601Date(c.getShipDate()));
+		o.shipDate(c.getShipDate());
 		o.status(OrderStatus.PLACED);
 		return add(o);
 	}
 
-	private List<Tag> getTags(List<String> tags) {
-		List<Tag> l = new ArrayList<>();
-		for (String t : tags)
-			l.add(getOrCreateTag(t));
+	private Tag[] getTags(String[] tags) {
+		if (tags == null)
+			return null;
+		Tag[] l = new Tag[tags.length];
+		for (int i = 0; i < tags.length; i++)
+			l[i]= getOrCreateTag(tags[i]);
 		return l;
 	}
 
@@ -220,13 +237,6 @@ public class PetStore {
 		return add(new Tag().name(name));
 	}
 
-	public Pet update(Pet value) throws IdNotFound {
-		Pet old = petDb.replace(value.getId(), value);
-		if (old == null)
-			throw new IdNotFound(value.getId(), Pet.class);
-		return value;
-	}
-
 	public Order update(Order value) throws IdNotFound {
 		Order old = orderDb.replace(value.getId(), value);
 		if (old == null)
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStoreResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStoreResource.java
index dd490d6..1ee8ba3 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStoreResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetStoreResource.java
@@ -12,19 +12,27 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.examples.rest.petstore;
 
+import static org.apache.juneau.dto.html5.HtmlBuilder.*;
 import static org.apache.juneau.dto.swagger.ui.SwaggerUI.*;
+import static org.apache.juneau.http.HttpMethodName.*;
 import static org.apache.juneau.rest.annotation.HookEvent.*;
 import static org.apache.juneau.rest.helper.Ok.*;
 
 import java.util.*;
+import java.util.Map;
 
+import org.apache.juneau.dto.html5.*;
 import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
 import org.apache.juneau.microservice.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.annotation.Body;
+import org.apache.juneau.rest.annotation.Header;
 import org.apache.juneau.rest.exception.*;
 import org.apache.juneau.rest.helper.*;
 import org.apache.juneau.rest.widget.*;
+import org.apache.juneau.transforms.*;
 import org.apache.juneau.rest.converters.*;
 
 /**
@@ -69,7 +77,7 @@ import org.apache.juneau.rest.converters.*;
 		// Resolve recursive references when showing schema info in the swagger.
 		@Property(name=SWAGGERUI_resolveRefsMaxDepth, value="99")
 	},
-	swagger="$F{PetStoreResource.json}",
+	swagger=@ResourceSwagger("$F{PetStoreResource.json}"),
 	staticFiles={"htdocs:htdocs"}
 )
 public class PetStoreResource extends BasicRestServletJena {
@@ -103,12 +111,12 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="GET",
 		path="/pet",
 		summary="All pets in the store",
-		swagger={
-			"tags:['pet'],",
-			"parameters:[",
-				 Queryable.SWAGGER_PARAMS,
-			"]"
-		},
+		swagger=@MethodSwagger(
+			tags="pet",
+			parameters={
+				Queryable.SWAGGER_PARAMS
+			}
+		),
 		bpx="Pet: tags",
 		htmldoc=@HtmlDoc(
 			widgets={
@@ -132,10 +140,12 @@ public class PetStoreResource extends BasicRestServletJena {
 		path="/pet/{petId}",
 		summary="Find pet by ID",
 		description="Returns a single pet",
-		swagger={
-			"tags:[ 'pet' ],",
-			"security:[ { api_key:[] } ]"
-		}
+		swagger=@MethodSwagger(
+			tags="pet",
+			value={
+				"security:[ { api_key:[] } ]"
+			}
+		)
 	)
 	public Pet getPet(
 			@Path(description="ID of pet to return", example="123") long petId
@@ -148,15 +158,18 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="POST", 
 		path="/pet",
 		summary="Add a new pet to the store",
-		swagger={
-			"tags:['pet'],",
-			"security:[ { petstore_auth:['write:pets','read:pets'] } ]"
-		}
+		swagger=@MethodSwagger(
+			tags="pet",
+			value={
+				"security:[ { petstore_auth:['write:pets','read:pets'] } ]"
+			}
+		)
 	)
 	public Ok addPet(
-			@Body(description="Pet object that needs to be added to the store") CreatePet pet
+			@Body(description="Pet object that needs to be added to the store") PetCreate pet
 		) throws IdConflict, NotAcceptable, UnsupportedMediaType {
 		
+		JsonSerializer.DEFAULT_LAX_READABLE.println(pet);
 		store.create(pet);
 		return OK;
 	}
@@ -165,13 +178,15 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="PUT", 
 		path="/pet/{petId}",
 		summary="Update an existing pet",
-		swagger={
-			"tags:['pet'],",
-			"security:[ { petstore_auth: ['write:pets','read:pets'] } ]"
-		}
+		swagger=@MethodSwagger(
+			tags="pet",
+			value={
+				"security:[ { petstore_auth: ['write:pets','read:pets'] } ]"
+			}
+		)
 	)
 	public Ok updatePet(
-			@Body(description="Pet object that needs to be added to the store") Pet pet
+			@Body(description="Pet object that needs to be added to the store") PetUpdate pet
 		) throws IdNotFound, NotAcceptable, UnsupportedMediaType {
 		
 		store.update(pet);
@@ -180,13 +195,85 @@ public class PetStoreResource extends BasicRestServletJena {
 
 	@RestMethod(
 		name="GET", 
+		path="/pet/{petId}/edit",
+		summary="Pet edit page",
+		swagger=@MethodSwagger(
+			tags="pet",
+			value={
+				"security:[ { petstore_auth:['write:pets','read:pets'] } ]"
+			}
+		)
+	)
+	public Div editPetPage(
+			@Path(description="ID of pet to return", example="123") long petId
+		) throws IdConflict, NotAcceptable, UnsupportedMediaType {
+		
+		Pet pet = getPet(petId);
+		
+		return div(
+			form().id("form").action("servlet:/pet/" + petId).method(POST).children(
+				table(
+					tr(
+						th("Id:"),
+						td(input().name("id").type("text").value(petId).readonly(true)),
+						td(new Tooltip("(?)", "The name of the pet.", br(), "e.g. 'Fluffy'")) 
+					),
+					tr(
+						th("Name:"),
+						td(input().name("name").type("text").value(pet.getName())),
+						td(new Tooltip("(?)", "The name of the pet.", br(), "e.g. 'Fluffy'")) 
+					),
+					tr(
+						th("Species:"),
+						td(
+							select().name("species").children(
+								option("cat"), option("dog"), option("bird"), option("fish"), option("mouse"), option("rabbit"), option("snake")
+							).choose(pet.getSpecies())
+						),
+						td(new Tooltip("(?)", "The kind of animal.")) 
+					),
+					tr(
+						th("Price:"),
+						td(input().name("price").type("number").placeholder("1.0").step("0.01").min(1).max(100).value(pet.getPrice())),
+						td(new Tooltip("(?)", "The price to charge for this pet.")) 
+					),
+					tr(
+						th("Tags:"),
+						td(input().name("tags").type("text").value(Tag.asString(pet.getTags()))),
+						td(new Tooltip("(?)", "Arbitrary textual tags (comma-delimited).", br(), "e.g. 'fluffy,friendly'")) 
+					),
+					tr(
+						th("Status:"),
+						td(
+							select().name("status").children(
+								option("AVAILABLE"), option("PENDING"), option("SOLD")
+							).choose(pet.getStatus())
+						),
+						td(new Tooltip("(?)", "The current status of the animal.")) 
+					),
+					tr(
+						td().colspan(2).style("text-align:right").children(
+							button("reset", "Reset"),
+							button("button","Cancel").onclick("window.location.href='/'"),
+							button("submit", "Submit")
+						)
+					)
+				).style("white-space:nowrap")
+			)
+		);
+	}
+
+	@RestMethod(
+		name="GET", 
 		path="/pet/findByStatus",
 		summary="Finds Pets by status",
 		description="Multiple status values can be provided with comma separated strings.",
-		swagger={
-			"tags:['pet'],",
-			"security:[{ petstore_auth:[ 'write:pets','read:pets' ] } ]"
-		}
+		swagger=@MethodSwagger(
+			tags="pet",
+			value={
+				"security:[{ petstore_auth:[ 'write:pets','read:pets' ] } ]"
+			}
+		)
 	)
 	public Collection<Pet> findPetsByStatus(
 			@Query(
@@ -206,10 +293,12 @@ public class PetStoreResource extends BasicRestServletJena {
 		path="/pet/findByTags",
 		summary="Finds Pets by tags",
 		description="Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
-		swagger={
-			"tags:['pet'],",
-			"security:[ { petstore_auth:[ 'write:pets','read:pets' ] } ]"
-		}
+		swagger=@MethodSwagger(
+			tags="pet",
+			value={
+				"security:[ { petstore_auth:[ 'write:pets','read:pets' ] } ]"
+			}
+		)
 	)
 	@Deprecated
 	public Collection<Pet> findPetsByTags(
@@ -226,35 +315,15 @@ public class PetStoreResource extends BasicRestServletJena {
 	}
 
 	@RestMethod(
-		name="POST", 
-		path="/pet/{petId}",
-		summary="Updates a pet in the store with form data",
-		swagger={
-			"tags:[ 'pet' ],",
-			"security:[ { petstore_auth:[ 'write:pets', 'read:pets' ] } ]"
-		}
-	)
-	public Ok updatePetForm(
-			@Path(description="ID of pet that needs to be updated", example="123") long petId, 
-			@FormData(name="name", description="Updated name of the pet", example="'Scruffy'") String name, 
-			@FormData(name="status", description="Updated status of the pet", example="'AVAILABLE'") PetStatus status
-		) throws IdNotFound, NotAcceptable, UnsupportedMediaType {
-		
-		Pet pet = store.getPet(petId);
-		pet.name(name);
-		pet.status(status);
-		store.update(pet);
-		return OK;
-	}
-
-	@RestMethod(
 		name="DELETE", 
 		path="/pet/{petId}",
 		summary="Deletes a pet",
-		swagger={
-			"tags:[ 'pet' ],",
-			"security:[ { petstore_auth:[ 'write:pets','read:pets' ] } ]"
-		}
+		swagger=@MethodSwagger(
+			tags="pet",
+			value={
+				"security:[ { petstore_auth:[ 'write:pets','read:pets' ] } ]"
+			}
+		)
 	)
 	public Ok deletePet(
 			@Header(name="api_key", example="foobar") String apiKey, 
@@ -269,10 +338,12 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="POST", 
 		path="/pet/{petId}/uploadImage",
 		summary="Uploads an image",
-		swagger={
-			"tags:[ 'pet' ],",
-			"security:[ { petstore_auth:[ 'write:pets','read:pets' ] } ]"
-		}
+		swagger=@MethodSwagger(
+			tags="pet",
+			value={
+				"security:[ { petstore_auth:[ 'write:pets','read:pets' ] } ]"
+			}
+		)
 	)
 	public Ok uploadImage(
 			@Path(description="ID of pet to update", example="123") long petId, 
@@ -290,7 +361,10 @@ public class PetStoreResource extends BasicRestServletJena {
 	@RestMethod(
 		name="GET", 
 		path="/store",
-		summary="Navigation page"
+		summary="Store navigation page",
+		swagger=@MethodSwagger(
+			tags="store"
+		)
 	) 
 	public ResourceDescription[] getTopStorePage() {
 		return new ResourceDescription[] {
@@ -303,9 +377,20 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="GET",
 		path="/store/order",
 		summary="Petstore orders",
-		swagger={
-			"tags:['store']"
-		}
+		swagger=@MethodSwagger(
+			tags="store"
+		),
+		htmldoc=@HtmlDoc(
+			widgets={
+				QueryMenuItem.class,
+				AddOrderMenuItem.class
+			},
+			navlinks={
+				"INHERIT",                // Inherit links from class.
+				"[2]:$W{QueryMenuItem}",  // Insert QUERY link in position 2.
+				"[3]:$W{AddOrderMenuItem}"  // Insert ADD link in position 3.
+			}
+		)
 	) 
 	public Collection<Order> getOrders() throws NotAcceptable {
 		return store.getOrders();
@@ -316,9 +401,9 @@ public class PetStoreResource extends BasicRestServletJena {
 		path="/store/order/{orderId}",
 		summary="Find purchase order by ID",
 		description="Returns a purchase order by ID.",
-		swagger={
-			"tags:[ 'store' ]",
-		}
+		swagger=@MethodSwagger(
+			tags="store"
+		)
 	)
 	public Order getOrder(
 			@Path(description="ID of order to fetch", maximum="1000", minimum="101", example="123") long orderId
@@ -333,25 +418,33 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="POST", 
 		path="/store/order",
 		summary="Place an order for a pet",
-		swagger={
-			"tags:[ 'store' ]"
+		swagger=@MethodSwagger(
+			tags="store"
+		),
+		pojoSwaps={
+			DateSwap.ISO8601D.class
 		}
 	)
 	public Order placeOrder(
-			@Body(description="Order placed for purchasing the pet", example="{petId:456,quantity:100}") CreateOrder order
+			@FormData(name="petId", description="Pet ID") long petId,
+			@FormData(name="shipDate", description="Ship date") Date shipDate
 		) throws IdConflict, NotAcceptable, UnsupportedMediaType {
 		
-		return store.create(order);
+		CreateOrder co = new CreateOrder(petId, shipDate);
+		return store.create(co);
 	}
 
 	@RestMethod(
 		name="DELETE", 
 		path="/store/order/{orderId}",
 		summary="Delete purchase order by ID",
-		description="For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors.",
-		swagger={
-			"tags:[ 'store' ]"
-		}
+		description= {
+			"For valid response try integer IDs with positive integer value.",
+			"Negative or non-integer values will generate API errors."
+		},
+		swagger=@MethodSwagger(
+			tags="store"
+		)
 	)
 	public Ok deletePurchaseOrder(
 			@Path(description="ID of the order that needs to be deleted", minimum="1", example="5") long orderId
@@ -368,13 +461,15 @@ public class PetStoreResource extends BasicRestServletJena {
 		path="/store/inventory",
 		summary="Returns pet inventories by status",
 		description="Returns a map of status codes to quantities",
-		swagger={
-			"tags:[ 'store' ],",
-			"responses:{",
+		swagger=@MethodSwagger(
+			tags="store",
+			responses={
 				"200:{ 'x-example':{AVAILABLE:123} }",
-			"},",
-			"security:[ { api_key:[] } ]"
-		}
+			},
+			value={
+				"security:[ { api_key:[] } ]"
+			}
+		)
 	)
 	public Map<PetStatus,Integer> getStoreInventory() throws NotAcceptable {
 		return store.getInventory();
@@ -389,9 +484,9 @@ public class PetStoreResource extends BasicRestServletJena {
 		path="/user",
 		summary="Petstore users",
 		bpx="User: email,password,phone",
-		swagger={
-			"tags:['user']"
-		}
+		swagger=@MethodSwagger(
+			tags="user"
+		)
 	)
 	public Collection<User> getUsers() throws NotAcceptable {
 		return store.getUsers();
@@ -401,9 +496,9 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="GET", 
 		path="/user/{username}",
 		summary="Get user by user name",
-		swagger={
-			"tags:[ 'user' ]"
-		}
+		swagger=@MethodSwagger(
+			tags="user"
+		)
 	)
 	public User getUser(
 			@Path(description="The name that needs to be fetched. Use user1 for testing.") String username
@@ -417,9 +512,9 @@ public class PetStoreResource extends BasicRestServletJena {
 		path="/user",
 		summary="Create user",
 		description="This can only be done by the logged in user.",
-		swagger={
-			"tags:[ 'user' ]"
-		}
+		swagger=@MethodSwagger(
+			tags="user"
+		)
 	)
 	public Ok createUser(
 			@Body(description="Created user object") User user
@@ -433,9 +528,9 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="POST", 
 		path="/user/createWithArray",
 		summary="Creates list of users with given input array",
-		swagger={
-			"tags:[ 'user' ]"
-		}
+		swagger=@MethodSwagger(
+			tags="user"
+		)
 	)
 	public Ok createUsers(
 			@Body(description="List of user objects") User[] users
@@ -451,9 +546,9 @@ public class PetStoreResource extends BasicRestServletJena {
 		path="/user/{username}",
 		summary="Update user",
 		description="This can only be done by the logged in user.",
-		swagger={
-			"tags:[ 'user' ]"
-		}
+		swagger=@MethodSwagger(
+			tags="user"
+		)
 	)
 	public Ok updateUser(
 			@Path(description="Name that need to be updated") String username, 
@@ -469,9 +564,9 @@ public class PetStoreResource extends BasicRestServletJena {
 		path="/user/{username}",
 		summary="Delete user",
 		description="This can only be done by the logged in user.",
-		swagger={
-			"tags:[ 'user' ]"
-		}
+		swagger=@MethodSwagger(
+			tags="user"
+		)
 	)
 	public Ok deleteUser(
 			@Path(description="The name that needs to be deleted") String username
@@ -485,17 +580,17 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="GET", 
 		path="/user/login",
 		summary="Logs user into the system",
-		swagger={
-			"tags:[ 'user' ],",
-			"responses:{",
+		swagger=@MethodSwagger(
+			tags="user",
+			responses={
 				"200:{",
 					"headers:{",
 						"X-Rate-Limit:{ type:'integer', format:'int32', description:'calls per hour allowed by the user', 'x-example':123},",
 						"X-Expires-After:{ type:'string', format:'date-time', description:'date in UTC when token expires', 'x-example':'2012-10-21'}",
 					"}",
-				"}",
-			"}"
-		}
+				"}"
+			}
+		)
 	)
 	public Ok login(
 			@Query(name="username", description="The username for login", required="true", example="myuser") String username, 
@@ -518,9 +613,9 @@ public class PetStoreResource extends BasicRestServletJena {
 		name="GET", 
 		path="/user/logout",
 		summary="Logs out current logged in user session",
-		swagger={
-			"tags:[ 'user' ]"
-		}
+		swagger=@MethodSwagger(
+			tags="user"
+		)
 	)
 	public Ok logout(RestRequest req) throws NotAcceptable {
 		req.getSession().removeAttribute("login-expires");
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreatePet.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetUpdate.java
similarity index 81%
rename from juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreatePet.java
rename to juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetUpdate.java
index 2e91036..c210fe9 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/CreatePet.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/PetUpdate.java
@@ -12,24 +12,23 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.examples.rest.petstore;
 
-import java.util.*;
-
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.utils.*;
 
 /**
  * Bean for creating {@link Pet} objects.
  */
-public class CreatePet {
+public class PetUpdate {
 
+	private final long id;
 	private final String name;
 	private final float price;
 	private final String species;
-	private final List<String> tags;
+	private final String[] tags;
 	private final PetStatus status;
 	
-	@BeanConstructor(properties="name,price,species,tags,status")
-	public CreatePet(String name, float price, String species, List<String> tags, PetStatus status) {
+	@BeanConstructor(properties="id,name,price,species,tags,status")
+	public PetUpdate(long id, String name, float price, String species, String[] tags, PetStatus status) {
+		this.id = id;
 		this.name = name;
 		this.price = price;
 		this.species = species;
@@ -37,8 +36,12 @@ public class CreatePet {
 		this.status = status;
 	}
 	
-	public static CreatePet example() {
-		return new CreatePet("Doggie", 9.99f, "doc", AList.create("friendly","cute"), PetStatus.AVAILABLE);
+	public static PetUpdate example() {
+		return new PetUpdate(123l, "Doggie", 9.99f, "doc", new String[] {"friendly","cute"}, PetStatus.AVAILABLE);
+	}
+
+	public long getId() {
+		return id;
 	}
 
 	public String getName() {
@@ -53,7 +56,7 @@ public class CreatePet {
 		return species;
 	}
 
-	public List<String> getTags() {
+	public String[] getTags() {
 		return tags;
 	}
 
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Species.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Species.java
index 09b5a4c..9fb4331 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Species.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Species.java
@@ -59,4 +59,9 @@ public class Species {
 			return "background-color:#FDF2E9";
 		}
 	}
+	
+	@Override /* Object */
+	public String toString() {
+		return name;
+	}
 }
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Tag.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Tag.java
index 649c38c..afd8c27 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Tag.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/petstore/Tag.java
@@ -12,9 +12,12 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.examples.rest.petstore;
 
+import java.util.*;
+
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.http.*;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.transform.*;
 
 @Bean(typeName="Tag", fluentSetters=true)
@@ -58,4 +61,13 @@ public class Tag {
 			return new MediaType[] { MediaType.HTML };
 		}
 	}
+	
+	public static String asString(List<Tag> tags) {
+		if (tags == null)
+			return "";
+		List<String> l = new ArrayList<>(tags.size());
+		for (Tag t : tags)
+			l.add(t.getName());
+		return StringUtils.join(l, ',');
+	}
 }
diff --git a/juneau-examples/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/petstore/Orders.json b/juneau-examples/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/petstore/Orders.json
index 15e5ab0..b306aa8 100644
--- a/juneau-examples/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/petstore/Orders.json
+++ b/juneau-examples/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/petstore/Orders.json
@@ -12,7 +12,7 @@
 // ***************************************************************************************************************************
 
 [
-	{id:101,petId:101,quantity:1,shipDate:'2018-01-01',status:'PLACED'},
-	{id:102,petId:102,quantity:5,shipDate:'2018-01-01',status:'APPROVED'},
-	{id:103,petId:103,quantity:10,shipDate:'2018-01-01',status:'DELIVERED'}
+	{id:101,petId:101,shipDate:'2018-01-01',status:'PLACED'},
+	{id:102,petId:102,shipDate:'2018-01-01',status:'APPROVED'},
+	{id:103,petId:103,shipDate:'2018-01-01',status:'DELIVERED'}
 ]
diff --git a/juneau-examples/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/RootContentTest.java b/juneau-examples/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/RootContentTest.java
index 85097bf..1b0dedf 100644
--- a/juneau-examples/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/RootContentTest.java
+++ b/juneau-examples/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/RootContentTest.java
@@ -39,7 +39,7 @@ public class RootContentTest extends ContentComboTestBase {
 				new ComboInput("HTML-header", "/", MediaType.HTML,
 					"<head>",
 					"<h1>Root resources</h1>",
-					"<h2>Example of a router resource page.</h2>",
+					"<h2>Navigation page</h2>",
 					"<img src='/htdocs/images/juneau.png' style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/>"
 				)
 			},
diff --git a/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java b/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
index 27863c1..5203a2b 100755
--- a/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
+++ b/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
@@ -50,11 +50,11 @@ public class ConfigResource extends BasicRestServlet {
 		path="/", 
 		summary="Get config file contents",
 		description="Show contents of config file as an ObjectMap.",
-		swagger={
-			"responses:{",
-				"200:{ description:'Config file as a map of map of objects.', 'x-example':{'':{defaultKey:'defaultValue'},'Section1':{key1:'val1',key2:123}}}",
-			"}",
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200:{ description:'Config file as a map of map of objects.', 'x-example':{'':{defaultKey:'defaultValue'},'Section1':{key1:'val1',key2:123}}}"
+			}
+		)
 	)
 	public ObjectMap getConfig() {
 		return getServletConfig().getConfig().asMap();
@@ -86,11 +86,11 @@ public class ConfigResource extends BasicRestServlet {
 		path="/{section}",
 		summary="Get config file section contents",
 		description="Show contents of config file section as an ObjectMap.",
-		swagger={
-			"responses:{",
-				"200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}",
-			"}",
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}"
+			}
+		)
 	)
 	public ObjectMap getConfigSection(
 			@Path(name="section", description="Section name in config file.", example="REST") String section
@@ -104,11 +104,11 @@ public class ConfigResource extends BasicRestServlet {
 		path="/{section}/{key}",
 		summary="Get config file entry value",
 		description="Show value of config file entry as a simple string.",
-		swagger={
-			"responses:{",
-				"200:{ description:'Entry value.', 'x-example':'servlet:/htdocs/themes/dark.css'}",
-			"}",
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200:{ description:'Entry value.', 'x-example':'servlet:/htdocs/themes/dark.css'}"
+			}
+		)
 	)
 	public String getConfigEntry(
 			@Path(name="section", description="Section name in config file.", example="REST") String section,
@@ -123,11 +123,11 @@ public class ConfigResource extends BasicRestServlet {
 		path="/",
 		summary="Update config file contents",
 		description="Update the contents of the config file from a FORM post.",
-		swagger={
-			"responses:{",
-				"200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}",
-			"}",
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}"
+			}
+		)
 	)
 	public ObjectMap setConfigContentsFormPost(
 			@FormData(name="contents", description="New contents in INI file format.") String contents
@@ -141,11 +141,11 @@ public class ConfigResource extends BasicRestServlet {
 		path="/",
 		summary="Update config file contents",
 		description="Update the contents of the config file from raw text.",
-		swagger={
-			"responses:{",
-				"200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}",
-			"}",
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}"
+			}
+		)
 	)
 	public ObjectMap setConfigContents(
 			@Body(description="New contents in INI file format.") Reader contents
@@ -159,11 +159,11 @@ public class ConfigResource extends BasicRestServlet {
 		path="/{section}",
 		summary="Update config section contents",
 		description="Add or overwrite a config file section.",
-		swagger={
-			"responses:{",
-				"200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}",
-			"}",
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200:{ description:'Config file section as a map of objects.', 'x-example':{key1:'val1',key2:123}}"
+			}
+		)
 	)
 	public ObjectMap setConfigSection(
 			@Path(name="section", description="Section name in config file.", example="REST") String section,
@@ -182,11 +182,11 @@ public class ConfigResource extends BasicRestServlet {
 		path="/{section}/{key}",
 		summary="Update config entry value",
 		description="Add or overwrite a config file entry.",
-		swagger={
-			"responses:{",
-				"200:{ description:'The updated value.', 'x-example':'servlet:/htdocs/themes/dark.css'}",
-			"}",
-		}
+		swagger=@MethodSwagger(
+			responses={
+				"200:{ description:'The updated value.', 'x-example':'servlet:/htdocs/themes/dark.css'}"
+			}
+		)
 	)
 	public String setConfigValue(
 			@Path(name="section", description="Section name in config file.", example="REST") String section,
diff --git a/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java b/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
index eb6a6b7..aa8c40d 100755
--- a/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
+++ b/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
@@ -144,7 +144,7 @@ public class LogsResource extends BasicRestServlet {
 
 		File f = getFile(path);
 
-		Date startDate = parseISO8601Date(start), endDate = parseISO8601Date(end);
+		Date startDate = parseIsoDate(start), endDate = parseIsoDate(end);
 
 		if (! highlight) {
 			Object o = getReader(f, startDate, endDate, thread, loggers, severity);
@@ -194,11 +194,11 @@ public class LogsResource extends BasicRestServlet {
 		htmldoc=@HtmlDoc(
 			nav={"<h5>Folder:  $RA{fullPath}</h5>"}
 		),
-		swagger={
-			"parameters:[",
-				 Queryable.SWAGGER_PARAMS,
-			"]"
-		}
+		swagger=@MethodSwagger(
+			parameters={
+				 Queryable.SWAGGER_PARAMS
+			}
+		)
 	)
 	public LogParser viewParsedEntries(
 			RestRequest req,
@@ -213,7 +213,7 @@ public class LogsResource extends BasicRestServlet {
 		File f = getFile(path);
 		req.setAttribute("fullPath", f.getAbsolutePath());
 
-		Date startDate = parseISO8601Date(start), endDate = parseISO8601Date(end);
+		Date startDate = parseIsoDate(start), endDate = parseIsoDate(end);
 
 		return getLogParser(f, startDate, endDate, thread, loggers, severity);
 	}
diff --git a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/NlsResource.java b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/NlsResource.java
index 279e702..eead39d 100644
--- a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/NlsResource.java
+++ b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/NlsResource.java
@@ -50,8 +50,8 @@ public class NlsResource extends BasicRestServletGroup {
 		@RestMethod(
 			name=POST, path="/{a}",
 			description="Test1.c",
-			swagger= {
-				"parameters:[",
+			swagger=@MethodSwagger(
+				parameters={
 					"{name:'a',in:'path',type:'string',description:'Test1.d'},",
 					"{name:'b',in:'query',type:'string',description:'Test1.e'},",
 					"{in:'body',type:'string',description:'Test1.f'},",
@@ -59,12 +59,12 @@ public class NlsResource extends BasicRestServletGroup {
 					"{name:'a2',in:'path',type:'string',description:'Test1.h'},",
 					"{name:'b2',in:'query',type:'string',description:'Test1.i'},",
 					"{name:'D2',in:'header',type:'string',description:'Test1.j'}",
-				"],",
-				"responses:{",
+				},
+				responses={
 					"200: {description:'OK'},",
 					"201: {description:'Test1.l',headers:{bar:{description:'Test1.m',type:'string'}}}",
-				"}"
-			}
+				}
+			)
 		)
 		public String test1(@Path("a") String a, @Query("b") String b, @Body String c, @Header("D") String d,
 				@Path("e") String e, @Query("f") String f, @Header("g") String g) {
@@ -165,21 +165,21 @@ public class NlsResource extends BasicRestServletGroup {
 		@RestMethod(
 			name=POST, path="/{a}",
 			description="$L{foo}",
-			swagger= {
-				"parameters:[",
+			swagger=@MethodSwagger(
+				parameters={
 					"{name:'a',in:'path',description:'$L{foo}'},",
 					"{name:'b',in:'query',description:'$L{foo}'},",
 					"{in:'body',description:'$L{foo}'},",
 					"{name:'D',in:'header',description:'$L{foo}'},",
 					"{name:'a2',in:'path',description:'$L{foo}'},",
 					"{name:'b2',in:'query',description:'$L{foo}'},",
-					"{name:'D2',in:'header',description:'$L{foo}'}",
-				"],",
-				"responses:{",
+					"{name:'D2',in:'header',description:'$L{foo}'}"
+				},
+				responses= {
 					"200: {description:'OK'},",
 					"201: {description:'$L{foo}',headers:{bar:{description:'$L{foo}',type:'string'}}}",
-				"}"
-			}
+				}
+			)
 		)
 		public String test6(@Path("a") String a, @Query("b") String b, @Body String c, @Header("D") String d,
 				@Path("e") String e, @Query("f") String f, @Header("g") String g) {
diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/JacocoDummyTest.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/JacocoDummyTest.java
index c1496ce..7761000 100644
--- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/JacocoDummyTest.java
+++ b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/JacocoDummyTest.java
@@ -14,7 +14,7 @@ package org.apache.juneau.rest.test;
 
 import java.lang.reflect.*;
 
-import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.util.*;
 import org.junit.*;
 
 public class JacocoDummyTest {
diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/RestUtilsTest.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/RestUtilsTest.java
index 513e4de..040f61e 100644
--- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/RestUtilsTest.java
+++ b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/RestUtilsTest.java
@@ -13,7 +13,7 @@
 package org.apache.juneau.rest.test;
 
 import static org.apache.juneau.internal.StringUtils.*;
-import static org.apache.juneau.rest.RestUtils.*;
+import static org.apache.juneau.rest.util.RestUtils.*;
 import static org.junit.Assert.*;
 
 import java.util.*;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
index fc07d68..5077ae3 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
@@ -26,6 +26,7 @@ import javax.servlet.http.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.exception.*;
 import org.apache.juneau.rest.helper.*;
+import org.apache.juneau.rest.util.*;
 import org.apache.juneau.rest.vars.*;
 
 /**
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java
index 566f366..dee7a7f 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java
@@ -33,6 +33,7 @@ import org.apache.juneau.json.*;
 import org.apache.juneau.jsonschema.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.util.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.svl.*;
 import org.apache.juneau.utils.*;
@@ -146,7 +147,6 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 	 * 	<br>Never <jk>null</jk>.
 	 * @throws Exception
 	 */
-	@SuppressWarnings("unchecked")
 	@Override /* RestInfoProvider */
 	public Swagger getSwagger(RestRequest req) throws Exception {
 		
@@ -182,55 +182,46 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 		
 		// Combine it with @RestResource(swagger)
 		for (Map.Entry<Class<?>,RestResource> e : findAnnotationsMapParentFirst(RestResource.class, context.getResource().getClass()).entrySet()) {
-			RestResource r = e.getValue();
-			if (r.swagger().length > 0) {
-				try {
-					omSwagger.putAll(parseObjectMap(vr.resolve(join(r.swagger(), '\n').trim()), true));
-				} catch (ParseException x) {
-					throw new SwaggerException(x, e.getKey(), "Malformed swagger JSON encountered in @RestResource(swagger).");
-				}
+			RestResource rr = e.getValue();
+
+			if (! rr.title().isEmpty())
+				omSwagger.getObjectMap("info", true).appendIf(true, true, true, "title", vr.resolve(rr.title()));
+			if (! rr.description().isEmpty())
+				omSwagger.getObjectMap("info", true).appendIf(true, true, true, "description", vr.resolve(rr.description()));
+			
+			ResourceSwagger r = rr.swagger();
+			
+			omSwagger.putAll(parseMap(join(r.value()), vr, true, false, "@ResourceSwagger(value) on class {0}", c));
+			
+			if (r.title().length + r.description().length + r.version().length() + r.contact().length + r.license().length + r.termsOfService().length > 0) {
+				ObjectMap info = omSwagger.getObjectMap("info", true);
+				info.appendIf(true, true, true, "title", vr.resolve(join(r.title())));
+				info.appendIf(true, true, true, "description", vr.resolve(join(r.description())));
+				info.appendIf(true, true, true, "version", vr.resolve(join(r.version())));
+				info.appendIf(true, true, true, "termsOfService", vr.resolve(join(r.termsOfService())));
+				info.appendIf(true, true, true, "contact", parseMap(join(r.contact()), vr, false, true, "@ResourceSwagger(contact) on class {0}", c));
+				info.appendIf(true, true, true, "license", parseMap(join(r.license()), vr, false, true, "@ResourceSwagger(license) on class {0}", c));
 			}
+
+			omSwagger.appendIf(true, true, true, "externalDocs", parseMap(join(r.externalDocs()), vr, false, true, "@ResourceSwagger(externalDocs) on class {0}", c));
+			omSwagger.appendIf(true, true, true, "tags", parseList(join(r.tags()), vr, false, true, "@ResourceSwagger(tags) on class {0}", c));
 		}
+
+		omSwagger.appendIf(true, true, true, "externalDocs", parseMap(mb.findFirstString(locale, "externalDocs"), vr, false, true, "Messages/externalDocs on class {0}", c));
 		
-		ObjectMap 
-			info = omSwagger.getObjectMap("info", true),
-			externalDocs = omSwagger.getObjectMap("externalDocs", true),
-			definitions = omSwagger.getObjectMap("definitions", true);
+		ObjectMap info = omSwagger.getObjectMap("info", true);
+		info.appendIf(true, true, true, "title", vr.resolve(mb.findFirstString(locale, "title")));
+		info.appendIf(true, true, true, "description", vr.resolve(mb.findFirstString(locale, "description")));
+		info.appendIf(true, true, true, "version", vr.resolve(mb.findFirstString(locale, "version")));
+		info.appendIf(true, true, true, "termsOfService", vr.resolve(mb.findFirstString(locale, "termsOfService")));
+		info.appendIf(true, true, true, "contact", parseMap(mb.findFirstString(locale, "contact"), vr, false, true, "Messages/contact on class {0}", c));
+		info.appendIf(true, true, true, "license", parseMap(mb.findFirstString(locale, "license"), vr, false, true, "Messages/license on class {0}", c));
+
 		ObjectList
 			produces = omSwagger.getObjectList("produces", true),
 			consumes = omSwagger.getObjectList("consumes", true);
-		
-		String s = this.title;
-		if (s == null)
-			s = mb.findFirstString(locale, "title");
-		if (s != null) 
-			info.put("title", vr.resolve(s));
-
-		s = this.description;
-		if (s == null)
-			s = mb.findFirstString(locale, "description");
-		if (s != null) 
-			info.put("description", vr.resolve(s));
-		
-		s = mb.findFirstString(locale, "version");
-		if (s != null) 
-			info.put("version", vr.resolve(s));
-		
-		s = mb.findFirstString(locale, "contact");
-		if (s != null) 
-			info.put("contact", jp.parse(vr.resolve(s), ObjectMap.class));
-		
-		s = mb.findFirstString(locale, "license");
-		if (s != null) 
-			info.put("license", jp.parse(vr.resolve(s), ObjectMap.class));
-		
-		s = mb.findFirstString(locale, "termsOfService");
-		if (s != null) 
-			info.put("termsOfService", vr.resolve(s));
-		
 		if (consumes.isEmpty()) 
 			consumes.addAll(req.getContext().getConsumes());
-
 		if (produces.isEmpty()) 
 			produces.addAll(req.getContext().getProduces());
 		
@@ -239,17 +230,17 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 			for (ObjectMap om : omSwagger.getObjectList("tags").elements(ObjectMap.class)) {
 				String name = om.getString("name");
 				if (name == null)
-					throw new SwaggerException(c, "Tag definition found without name in swagger JSON.");
+					throw new SwaggerException(null, "Tag definition found without name in swagger JSON.");
 				tagMap.put(name, om);
 			}
 		}
 		
-		s = mb.findFirstString(locale, "tags");
+		String s = mb.findFirstString(locale, "tags");
 		if (s != null) {
-			for (ObjectMap m : jp.parse(vr.resolve(s), ObjectList.class).elements(ObjectMap.class)) {
+			for (ObjectMap m : parseList(s, vr, true, false, "Messages/tags on class {0}", c).elements(ObjectMap.class)) {
 				String name = m.getString("name");
 				if (name == null)
-					throw new SwaggerException(c, "Tag definition found without name in resource bundle.");
+					throw new SwaggerException(null, "Tag definition found without name in resource bundle on class {0}", c) ;
 				if (tagMap.containsKey(name))
 					tagMap.get(name).putAll(m);
 				else
@@ -257,11 +248,8 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 			}
 		}
 
-		s = mb.findFirstString(locale, "externalDocs");
-		if (s != null) 
-			externalDocs.putAll(jp.parse(vr.resolve(s), ObjectMap.class));
-
 		// Load our existing bean definitions into our session.
+		ObjectMap definitions = omSwagger.getObjectMap("definitions", true);
 		for (String defId : definitions.keySet()) 
 			js.addBeanDef(defId, definitions.getObjectMap(defId));
 		
@@ -280,61 +268,46 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 			ObjectMap op = getOperation(omSwagger, sm.getPathPattern(), sm.getHttpMethod().toLowerCase());
 			
 			// Add @RestMethod(swagger)
-			if (rm.swagger().length > 0) {
-				try {
-					op.putAll(parseObjectMap( vr.resolve(join(rm.swagger(), '\n').trim()), true));
-				} catch (ParseException e) {
-					throw new SwaggerException(e, m, "Malformed swagger JSON encountered in @RestMethod(swagger).");
-				}
-			}
+			MethodSwagger ms = rm.swagger();
+
+			op.putAll(parseMap(join(ms.value()), vr, true, false, "@MethodSwagger(value) on class {0} method {1}", c, m));
+			op.appendIf(true, true, true, "summary", vr.resolve(rm.summary()));
+			op.appendIf(true, true, true, "summary", vr.resolve(join(ms.summary())));
+			op.appendIf(true, true, true, "description", vr.resolve(join(rm.description())));
+			op.appendIf(true, true, true, "description", vr.resolve(join(ms.description())));
+			op.appendIf(true, true, true, "deprecated", vr.resolve(ms.deprecated()));
+			op.appendIf(true, true, true, "externalDocs", parseMap(join(ms.externalDocs()), vr, false, true, "@MethodSwagger(externalDocs) on class {0} method {1}", c, m));
+			
+			if (ms.parameters().length > 0)
+				op.getObjectList("parameters", true).addAll(parseList(join(ms.parameters()), vr, false, false, "@MethodSwagger(parameters) on class {0} method {1}", c, m));
+			if (ms.responses().length > 0)
+				op.getObjectMap("responses", true).putAll(parseMap(join(ms.responses()), vr, false, false, "@MethodSwagger(responses) on class {0} method {1}", c, m));
+			if (ms.tags().length > 0)
+				op.getObjectList("tags", true).addAll(parseListOrCdl(join(ms.tags()), vr, false, false, "@MethodSwagger(tags) on class {0} method {1}", c, m));
 
 			op.putIfNotExists("operationId", mn);
 			
 			if (m.getAnnotation(Deprecated.class) != null)
 				op.put("deprecated", true);
-				
-			s = rm.summary();
-			if (s.isEmpty())
-				s = mb.findFirstString(locale, mn + ".summary");
-			if (s != null)
-				op.put("summary", vr.resolve(s));
 
-			s = join(rm.description(), "");
-			if (s.isEmpty())
-				s = mb.findFirstString(locale, mn + ".description");
-			if (s != null)
-				op.put("description", vr.resolve(s));
+			op.appendIf(true, true, true, "summary", vr.resolve(mb.findFirstString(locale, mn + ".summary")));
+			op.appendIf(true, true, true, "description", vr.resolve(mb.findFirstString(locale, mn + ".description")));
 
-			Set<String> tags = new LinkedHashSet<>();
-			if (op.containsKey("tags"))
-				for (String tag : op.getObjectList("tags").elements(String.class)) 
-					tags.add(tag);
-			
 			s = mb.findFirstString(locale, mn + ".tags");
-			if (s != null) {
-				s = vr.resolve(s);
-				if (isObjectList(s, true))
-					tags.addAll((List<String>)jp.parse(s, ArrayList.class, String.class));
-				else
-					tags.addAll(Arrays.asList(StringUtils.split(s)));
-			}
+			if (s != null) 
+				op.getObjectList("tags", true).addAll(parseListOrCdl(s, vr, false, false, "Messages/tags on class {0} method {1}", c, m));
 			
-			for (String tag : tags) 
-				if (! tagMap.containsKey(tag))
-					tagMap.put(tag, new ObjectMap().append("name", tag));
+			if (op.containsKey("tags"))
+				for (String tag : op.getObjectList("tags").elements(String.class)) 
+					if (! tagMap.containsKey(tag))
+						tagMap.put(tag, new ObjectMap().append("name", tag));
 			
-			if (! tags.isEmpty())
-				op.put("tags", tags);
-			
-			s = mb.findFirstString(locale, mn + ".externalDocs");
-			if (s != null) {
-				ObjectMap eom = jp.parse(vr.resolve(s), ObjectMap.class);
-				if (op.containsKey("externalDocs"))
-					op.getObjectMap("externalDocs").putAll(eom);
-				else
-					op.put("externalDocs", eom);
-			}
+			op.appendIf(true, true, true, "externalDocs", parseMap(mb.findFirstString(locale, mn + ".description"), vr, false, true, "Messages/externalDocs on class {0} method {1}", c, m));
 			
+			s = mb.findFirstString(locale, mn + ".parameters");
+			if (s != null) 
+				op.getObjectList("parameters", true).addAll(parseList(s, vr, false, false, "Messages/parameters on class {0} method {1}", c, m));
+
 			ObjectMap paramMap = new ObjectMap();
 
 			ObjectList ol = op.getObjectList("parameters");
@@ -342,18 +315,6 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 				for (ObjectMap param : ol.elements(ObjectMap.class)) 
 					paramMap.put(param.getString("in") + '.' + param.getString("name"), param);
 		
-			s = mb.findFirstString(locale, mn + ".parameters");
-			if (s != null) {
-				ol = jp.parse(vr.resolve(s), ObjectList.class);
-				for (ObjectMap param : ol.elements(ObjectMap.class)) {
-					String key = param.getString("in") + '.' + param.getString("name");
-					if (paramMap.containsKey(key))
-						paramMap.getObjectMap(key).putAll(param);
-					else
-						paramMap.put(key, param);
-				}
-			}
-			
 			// Finally, look for parameters defined on method.
 			for (RestMethodParam mp : context.getRestMethodParams(m)) {
 				
@@ -372,50 +333,29 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 					param.append("name", mp.name);
 				
 				ObjectMap pi = mp.getMetaData();
-				if (pi.containsKeyNotEmpty("required"))
-					param.appendIf(false, true, true, "required", vr.resolve(pi.getString("required")));
-				if (pi.containsKeyNotEmpty("description"))
-					param.appendIf(false, true, true, "description", vr.resolve(pi.getString("description")));
-				if (pi.containsKeyNotEmpty("type"))
-					param.appendIf(false, true, true, "type", vr.resolve(pi.getString("type")));
-				if (pi.containsKeyNotEmpty("format"))
-					param.appendIf(false, true, true, "format", vr.resolve(pi.getString("format")));
-				if (pi.containsKeyNotEmpty("pattern"))
-					param.appendIf(false, true, true, "pattern", vr.resolve(pi.getString("pattern")));
-				if (pi.containsKeyNotEmpty("collectionFormat"))
-					param.appendIf(false, true, true, "collectionFormat", vr.resolve(pi.getString("collectionFormat")));
-				if (pi.containsKeyNotEmpty("maximum"))
-					param.appendIf(false, true, true, "maximum", vr.resolve(pi.getString("maximum")));
-				if (pi.containsKeyNotEmpty("minimum"))
-					param.appendIf(false, true, true, "minimum", vr.resolve(pi.getString("minimum")));
-				if (pi.containsKeyNotEmpty("multipleOf"))
-					param.appendIf(false, true, true, "multipleOf", vr.resolve(pi.getString("multipleOf")));
-				if (pi.containsKeyNotEmpty("maxLength"))
-					param.appendIf(false, true, true, "maxLength", vr.resolve(pi.getString("maxLength")));
-				if (pi.containsKeyNotEmpty("minLength"))
-					param.appendIf(false, true, true, "minLength", vr.resolve(pi.getString("minLength")));
-				if (pi.containsKeyNotEmpty("maxItems"))
-					param.appendIf(false, true, true, "maxItems", vr.resolve(pi.getString("maxItems")));
-				if (pi.containsKeyNotEmpty("minItems"))
-					param.appendIf(false, true, true, "minItems", vr.resolve(pi.getString("minItems")));
-				if (pi.containsKeyNotEmpty("allowEmptyVals"))
-					param.appendIf(false, true, true, "allowEmptyVals", vr.resolve(pi.getString("allowEmptyVals")));
-				if (pi.containsKeyNotEmpty("exclusiveMaximum"))
-					param.appendIf(false, true, true, "exclusiveMaximum", vr.resolve(pi.getString("exclusiveMaximum")));
-				if (pi.containsKeyNotEmpty("exclusiveMimimum"))
-					param.appendIf(false, true, true, "exclusiveMimimum", vr.resolve(pi.getString("exclusiveMimimum")));
-				if (pi.containsKeyNotEmpty("uniqueItems"))
-					param.appendIf(false, true, true, "uniqueItems", vr.resolve(pi.getString("uniqueItems")));
-				if (pi.containsKeyNotEmpty("schema"))
-					param.appendIf(false, true, true, "schema", parseObjectMap(vr.resolve(pi.getString("schema")), false));
-				if (pi.containsKeyNotEmpty("default"))
-					param.appendIf(false, true, true, "default", JsonParser.DEFAULT.parse(vr.resolve(pi.getString("default")), Object.class));
-				if (pi.containsKeyNotEmpty("enum"))
-					param.appendIf(false, true, true, "enum", parseObjectList(vr.resolve(pi.getString("enum")), false));
+				param.appendIf(false, true, true, "required", vr.resolve(pi.getString("required")));
+				param.appendIf(false, true, true, "description", vr.resolve(pi.getString("description")));
+				param.appendIf(false, true, true, "type", vr.resolve(pi.getString("type")));
+				param.appendIf(false, true, true, "format", vr.resolve(pi.getString("format")));
+				param.appendIf(false, true, true, "pattern", vr.resolve(pi.getString("pattern")));
+				param.appendIf(false, true, true, "collectionFormat", vr.resolve(pi.getString("collectionFormat")));
+				param.appendIf(false, true, true, "maximum", vr.resolve(pi.getString("maximum")));
+				param.appendIf(false, true, true, "minimum", vr.resolve(pi.getString("minimum")));
+				param.appendIf(false, true, true, "multipleOf", vr.resolve(pi.getString("multipleOf")));
+				param.appendIf(false, true, true, "maxLength", vr.resolve(pi.getString("maxLength")));
+				param.appendIf(false, true, true, "minLength", vr.resolve(pi.getString("minLength")));
+				param.appendIf(false, true, true, "maxItems", vr.resolve(pi.getString("maxItems")));
+				param.appendIf(false, true, true, "minItems", vr.resolve(pi.getString("minItems")));
+				param.appendIf(false, true, true, "allowEmptyVals", vr.resolve(pi.getString("allowEmptyVals")));
+				param.appendIf(false, true, true, "exclusiveMaximum", vr.resolve(pi.getString("exclusiveMaximum")));
+				param.appendIf(false, true, true, "exclusiveMimimum", vr.resolve(pi.getString("exclusiveMimimum")));
+				param.appendIf(false, true, true, "uniqueItems", vr.resolve(pi.getString("uniqueItems")));
+				param.appendIf(false, true, true, "schema", parseMap(pi.getString("schema"), vr, false, true, "ParameterInfo/schema on class {0} method {1}", c, m));
+				param.appendIf(false, true, true, "default", JsonParser.DEFAULT.parse(vr.resolve(pi.getString("default")), Object.class));
+				param.appendIf(false, true, true, "enum", parseList(pi.getString("enum"), vr, false, true, "ParameterInfo/enum on class {0} method {1}", c, m));
+				param.appendIf(false, true, true, "x-example", parseAnything(vr.resolve(pi.getString("example"))));
 				if (pi.containsKeyNotEmpty("items"))
 					param.appendIf(false, true, true, "items", new ObjectMap(vr.resolve(pi.getString("items"))));
-				if (pi.containsKeyNotEmpty("example"))
-					param.appendIf(false, true, true, "x-example", parse(vr.resolve(pi.getString("example"))));
 				
 				if ((in == BODY || in == PATH) && ! param.containsKeyNotEmpty("required"))
 					param.put("required", true);
@@ -444,18 +384,12 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 			for (RestMethodThrown rt : context.getRestMethodThrowns(m)) {
 				int code = rt.getCode();
 				if (code != 0) {
-					ObjectMap om = responses.getObjectMap(String.valueOf(code), true);
-					
 					ObjectMap md = rt.getMetaData();
-					if (md.containsKeyNotEmpty("description"))
-						om.appendIf(false, true, true, "description", vr.resolve(md.getString("description")));
-					if (md.containsKeyNotEmpty("example"))
-						om.appendIf(false, true, true, "x-example", parse(vr.resolve(md.getString("example"))));
-					if (md.containsKeyNotEmpty("schema"))
-						om.appendIf(false, true, true, "schema", parseObjectMap(vr.resolve(md.getString("schema")), false));
-					if (md.containsKeyNotEmpty("headers")) {
-						om.appendIf(false, true, true, "headers", parseObjectList(vr.resolve(md.getString("headers")), false));
-					}
+					ObjectMap om = responses.getObjectMap(String.valueOf(code), true);
+					om.appendIf(false, true, true, "description", vr.resolve(md.getString("description")));
+					om.appendIf(false, true, true, "x-example", parseAnything(vr.resolve(md.getString("example"))));
+					om.appendIf(false, true, true, "schema", parseMap(md.getString("schema"), vr, false, true, "RestMethodThrown/schema on class {0} method {1}", c, m));
+					om.appendIf(false, true, true, "headers", parseList(md.getString("headers"), vr, false, true, "RestMethodThrown/headers on class {0} method {1}", c, m));
 				}
 			}
 			
@@ -465,14 +399,10 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 			ObjectMap rom = responses.getObjectMap(rStatus, true);
 
 			ObjectMap rmd = r.getMetaData();
-			if (rmd.containsKeyNotEmpty("description"))
-				rom.appendIf(false, true, true, "description", vr.resolve(rmd.getString("description")));
-			if (rmd.containsKeyNotEmpty("example"))
-				rom.appendIf(false, true, true, "x-example", parse(vr.resolve(rmd.getString("example"))));
-			if (rmd.containsKeyNotEmpty("schema"))
-				rom.appendIf(false, true, true, "schema", parseObjectMap(vr.resolve(rmd.getString("schema")), false));
-			if (rmd.containsKeyNotEmpty("headers")) 
-				rom.appendIf(false, true, true, "headers", parseObjectMap(vr.resolve(rmd.getString("headers")), false));
+			rom.appendIf(false, true, true, "description", vr.resolve(rmd.getString("description")));
+			rom.appendIf(false, true, true, "x-example", parseAnything(vr.resolve(rmd.getString("example"))));
+			rom.appendIf(false, true, true, "schema", parseMap(rmd.getString("schema"), vr, false, true, "RestMethodReturn/schema on class {0} method {1}", c, m));
+			rom.appendIf(false, true, true, "headers", parseMap(rmd.getString("headers"), vr, false, true, "RestMethodReturn/headers on class {0} method {1}", c, m));
 			
 			rom.put("schema", getSchema(req, rom.getObjectMap("schema", true), js, m.getGenericReturnType()));
 			addXExamples(req, sm, rom, "ok", js, m.getGenericReturnType());
@@ -508,8 +438,6 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 		
 		if (definitions.isEmpty())
 			omSwagger.remove("definitions");		
-		if (externalDocs.isEmpty())
-			omSwagger.remove("externalDocs");
 		if (tagMap.isEmpty())
 			omSwagger.remove("tags");
 		
@@ -527,27 +455,65 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 		return swagger;
 	}
 	
-	private Object parse(String s) throws ParseException {
+	private String join(String...s) {
+		return StringUtils.join(s, '\n');
+	}
+	
+	private Object parseAnything(String s) throws ParseException {
+		if (s == null)
+			return null;
 		char c1 = StringUtils.firstNonWhitespaceChar(s), c2 = StringUtils.lastNonWhitespaceChar(s);
 		if (c1 == '{' && c2 == '}' || c1 == '[' && c2 == ']' || c1 == '\'' && c2 == '\'')
 			return JsonParser.DEFAULT.parse(s, Object.class);
 		return s;
 	}
 	
-	private ObjectMap parseObjectMap(String s, boolean ignoreCommentsAndWhitespace) throws ParseException {
-		s = s.trim();
-		if ("IGNORE".equalsIgnoreCase(s))
-			return new ObjectMap().append("ignore", true);
-		if (! isObjectMap(s, ignoreCommentsAndWhitespace))
-			s = "{" + s + "}";
-		return new ObjectMap(s);
+	private ObjectMap parseMap(String s, VarResolverSession vs, boolean ignoreCommentsAndWhitespace, boolean nullOnEmpty, String location, Object...locationArgs) throws ParseException {
+		try {
+			if (s == null)
+				return null;
+			if (s.isEmpty())
+				return nullOnEmpty ? null : ObjectMap.EMPTY_MAP;
+			s = vs.resolve(s.trim());
+			if ("IGNORE".equalsIgnoreCase(s))
+				return new ObjectMap().append("ignore", true);
+			if (! isObjectMap(s, ignoreCommentsAndWhitespace))
+				s = "{" + s + "}";
+			return new ObjectMap(s);
+		} catch (ParseException e) {
+			throw new SwaggerException(e, "Malformed swagger JSON object encountered in " + location + ".", locationArgs);
+		}
 	}	
 
-	private ObjectList parseObjectList(String s, boolean ignoreCommentsAndWhitespace) throws ParseException {
-		if (! isObjectList(s, ignoreCommentsAndWhitespace))
-			s = "[" + s + "]";
-		return new ObjectList(s);
+	private ObjectList parseList(String s, VarResolverSession vs, boolean ignoreCommentsAndWhitespace, boolean nullOnEmpty, String location, Object...locationArgs) throws ParseException {
+		try {
+			if (s == null)
+				return null;
+			if (s.isEmpty())
+				return nullOnEmpty ? null : ObjectList.EMPTY_LIST;
+			s = vs.resolve(s.trim());
+			if (! isObjectList(s, ignoreCommentsAndWhitespace))
+				s = "[" + s + "]";
+			return new ObjectList(s);
+		} catch (ParseException e) {
+			throw new SwaggerException(e, "Malformed swagger JSON array encountered in "+location+".", locationArgs);
+		}
 	}	
+	
+	private ObjectList parseListOrCdl(String s, VarResolverSession vs, boolean ignoreCommentsAndWhitespace, boolean nullOnEmpty, String location, Object...locationArgs) throws ParseException {
+		try {
+			if (s == null)
+				return null;
+			if (s.isEmpty())
+				return nullOnEmpty ? null : ObjectList.EMPTY_LIST;
+			s = vs.resolve(s.trim());
+			if (! isObjectList(s, ignoreCommentsAndWhitespace))
+				return new ObjectList(Arrays.asList(StringUtils.split(s, ',')));
+			return new ObjectList(s);
+		} catch (ParseException e) {
+			throw new SwaggerException(e, "Malformed swagger JSON array encountered in "+location+".", locationArgs);
+		}
+	}
 
 	private ObjectMap getSchema(RestRequest req, ObjectMap schema, JsonSchemaSerializerSession js, Type type) throws Exception {
 		BeanSession bs = req.getBeanSession();
@@ -651,16 +617,8 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 	private static class SwaggerException extends ParseException {
 		private static final long serialVersionUID = 1L;
 		
-		SwaggerException(Class<?> c, String message, Object...args) {
-			this(null, c, message, args);
-		}
-		SwaggerException(Exception e, Class<?> c, String message, Object...args) {
-			super("Swagger exception on class " + c.getName() + ".  " + message, args);
-			initCause(e);
-		}
-		SwaggerException(Exception e, Method m, String message, Object...args) {
-			super("Swagger exception on class " + m.getDeclaringClass().getName() + " method "+m.getName()+".  " + message, args);
-			initCause(e);
+		SwaggerException(Exception e, String location, Object...locationArgs) {
+			super(e, "Swagger exception:  at " + format(location, locationArgs));
 		}
 	}
 	
@@ -778,7 +736,7 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 	public String getMethodDescription(Method method, RestRequest req) throws Exception {
 		VarResolverSession vr = req.getVarResolverSession();
 		
-		String s = join(method.getAnnotation(RestMethod.class).description(), "");
+		String s = join(method.getAnnotation(RestMethod.class).description());
 		if (s.isEmpty()) {
 			Operation o = getSwaggerOperation(method, req);
 			if (o != null)
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServletGroup.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServletGroup.java
index 7e30ff7..7b38b53 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServletGroup.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServletGroup.java
@@ -43,7 +43,7 @@ public abstract class BasicRestServletGroup extends BasicRestServlet {
 	 * @return The bean containing links to the child resources.
 	 * @throws Exception 
 	 */
-	@RestMethod(name=GET, path="/", description="Child resources")
+	@RestMethod(name=GET, path="/", summary="Navigation page")
 	public ChildResourceDescriptions getChildren(RestRequest req) throws Exception {
 		return new ChildResourceDescriptions(getContext(), req);
 	}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java
index 22e91fe..e97a615 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java
@@ -27,6 +27,7 @@ import org.apache.juneau.http.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.exception.*;
+import org.apache.juneau.rest.util.*;
 
 /**
  * Contains the body of the HTTP request.
@@ -461,114 +462,4 @@ public class RequestBody {
 	public int getContentLength() {
 		return contentLength == 0 ? req.getRawContentLength() : contentLength;
 	}
-
-	/**
-	 * ServletInputStream wrapper around a normal input stream.
-	 */
-	static final class BoundedServletInputStream extends ServletInputStream {
-
-		private final InputStream is;
-		private final ServletInputStream sis;
-		private long remain;
-
-		BoundedServletInputStream(InputStream is, long max) {
-			this.is = is;
-			this.sis = null;
-			this.remain = max;
-		}
-
-		BoundedServletInputStream(ServletInputStream sis, long max) {
-			this.sis = sis;
-			this.is = sis;
-			this.remain = max;
-		}
-
-		BoundedServletInputStream(byte[] b) {
-			this(new ByteArrayInputStream(b), Long.MAX_VALUE);
-		}
-
-		@Override /* InputStream */
-		public final int read() throws IOException {
-			decrement();
-			return is.read();
-		}
-
-		@Override /* InputStream */
-		public int read(byte[] b) throws IOException {
-			return read(b, 0, b.length);
-		}
-
-		@Override /* InputStream */
-		public int read(final byte[] b, final int off, final int len) throws IOException {
-			long numBytes = Math.min(len, remain);
-			int r = is.read(b, off, (int) numBytes);
-			if (r == -1) 
-				return -1;
-			decrement(numBytes);
-			return r;
-		}
-
-		@Override /* InputStream */
-		public long skip(final long n) throws IOException {
-			long toSkip = Math.min(n, remain);
-			long r = is.skip(toSkip);
-			decrement(r);
-			return r;
-		}
-
-		@Override /* InputStream */
-		public int available() throws IOException {
-			if (remain <= 0)
-				return 0;
-			return is.available();
-		}
-
-		@Override /* InputStream */
-		public synchronized void reset() throws IOException {
-			is.reset();
-		}
-
-		@Override /* InputStream */
-		public synchronized void mark(int limit) {
-			is.mark(limit);
-		}
-
-		@Override /* InputStream */
-		public boolean markSupported() {
-			return is.markSupported();
-		}
-
-		@Override /* InputStream */
-		public final void close() throws IOException {
-			is.close();
-		}
-
-		@Override /* ServletInputStream */
-		public boolean isFinished() {
-			return sis == null ? false : sis.isFinished();
-		}
-
-		@Override /* ServletInputStream */
-		public boolean isReady() {
-			return sis == null ? true : sis.isReady();
-		}
-
-		@Override /* ServletInputStream */
-		public void setReadListener(ReadListener arg0) {
-			if (sis != null)
-				sis.setReadListener(arg0);
-		}
-		
-		private void decrement() throws IOException {
-			remain--;
-			if (remain < 0)
-				throw new IOException("Input limit exceeded.  See @RestResource.maxInput().");
-		}
-
-		private void decrement(long count) throws IOException {
-			remain -= count;
-			if (remain < 0)
-				throw new IOException("Input limit exceeded.  See @RestResource.maxInput().");
-		}
-	}
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 41ea56b..9730376 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -3159,7 +3159,7 @@ public final class RestContext extends BeanContext {
 				if (r instanceof RestServlet)
 					((RestServlet)r).innerInit(childBuilder);
 				childBuilder.servletContext(servletContext);
-				RestContext rc2 = new RestContext(childBuilder);
+				RestContext rc2 = childBuilder.build();
 				if (r instanceof RestServlet)
 					((RestServlet)r).setContext(rc2);
 				path = childBuilder.path;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
index 1d2b66c..4e2dfab 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
@@ -36,6 +36,7 @@ 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.util.*;
 import org.apache.juneau.rest.vars.*;
 import org.apache.juneau.rest.widget.*;
 import org.apache.juneau.serializer.*;
@@ -170,10 +171,12 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 			vr = varResolverBuilder.build();
 
 			// Add the servlet init parameters to our properties.
-			for (Enumeration<String> ep = servletConfig.getInitParameterNames(); ep.hasMoreElements();) {
-				String p = ep.nextElement();
-				String initParam = servletConfig.getInitParameter(p);
-				set(vr.resolve(p), vr.resolve(initParam));
+			if (servletConfig != null) {
+				for (Enumeration<String> ep = servletConfig.getInitParameterNames(); ep.hasMoreElements();) {
+					String p = ep.nextElement();
+					String initParam = servletConfig.getInitParameter(p);
+					set(vr.resolve(p), vr.resolve(initParam));
+				}
 			}
 
 			// Load stuff from parent-to-child order.
@@ -262,6 +265,17 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 		}
 	}
 	
+	@Override /* BeanContextBuilder */
+	public RestContext build() {
+		try {
+			return new RestContext(this);
+		} catch (RestException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+	
 	private static String[] resolveVars(VarResolver vr, String[] in) {
 		String[] out = new String[in.length];
 		for (int i = 0; i < in.length; i++) 
@@ -272,7 +286,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	/*
 	 * Calls all @RestHook(INIT) methods on the specified resource object.
 	 */
-	void init(Object resource) throws ServletException {
+	RestContextBuilder init(Object resource) throws ServletException {
 		this.resource = resource;
 
 		// Once we have the resource object, we can construct the Widgets.
@@ -311,6 +325,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 				throw new RestServletException("Exception thrown from @RestHook(INIT) method {0}.", m).initCause(e);
 			}
 		}
+		return this;
 	}
 	
 	RestContextBuilder servletContext(ServletContext servletContext) {
@@ -2343,10 +2358,4 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	public String getServletName() {
 		return inner.getServletName();
 	}
-
-	@Override /* BeanContextBuilder */
-	public BeanContext build() {
-		// We don't actually generate bean context objects from this class yet.
-		return null;
-	}
 }
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 4fea1a6..a183542 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
@@ -34,6 +34,7 @@ import org.apache.juneau.internal.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.exception.*;
+import org.apache.juneau.rest.util.*;
 import org.apache.juneau.rest.widget.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.svl.*;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index ca8e06b..85cbfca 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -39,6 +39,7 @@ import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.exception.*;
 import org.apache.juneau.rest.helper.*;
+import org.apache.juneau.rest.util.*;
 import org.apache.juneau.rest.widget.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.svl.*;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
index 03af644..e24cafa 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
@@ -27,6 +27,7 @@ import org.apache.juneau.http.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.exception.*;
+import org.apache.juneau.rest.util.*;
 import org.apache.juneau.serializer.*;
 
 /**
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java
index 67333c8..e436452 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java
@@ -51,7 +51,7 @@ public abstract class RestServlet extends HttpServlet {
 			super.init(servletConfig);
 			if (! isInitialized) {
 				builder.servletContext(this.getServletContext());
-				context = new RestContext(builder);
+				context = builder.build();
 				isInitialized = true;
 			}
 			context.postInit();
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/MethodSwagger.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/MethodSwagger.java
new file mode 100644
index 0000000..496a5c0
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/MethodSwagger.java
@@ -0,0 +1,245 @@
+// ***************************************************************************************************************************
+// * 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.annotation;
+
+/**
+ * Extended annotation for {@link RestMethod#swagger() RestMethod.swagger()}.
+ * 
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../overview-summary.html#juneau-rest-server.OptionsPages">Overview &gt; juneau-rest-server &gt; OPTIONS Pages</a>
+ * </ul>
+ */
+public @interface MethodSwagger {
+	
+
+	/**
+	 * Swagger JSON.
+	 * 
+	 * <p>
+	 * Used for free-form Swagger documentation of a REST resource.
+	 * 
+	 * 
+		swagger={
+			"tags:[ 'store' ],",
+			"responses:{",
+				"200:{ 'x-example':{AVAILABLE:123} }",
+			"},",
+			"security:[ { api_key:[] } ]"
+		}
+	)
+		swagger={
+			"tags:[ 'store' ],",
+			"responses:{",
+				"200:{ 'x-example':{AVAILABLE:123} }",
+			"},",
+			"security:[ { api_key:[] } ]"
+		}
+			swagger= {
+				"parameters:[",
+					"{name:'a',in:'path',type:'string',description:'Test1.d'},",
+					"{name:'b',in:'query',type:'string',description:'Test1.e'},",
+					"{in:'body',type:'string',description:'Test1.f'},",
+					"{name:'D',in:'header',type:'string',description:'Test1.g'},",
+					"{name:'a2',in:'path',type:'string',description:'Test1.h'},",
+					"{name:'b2',in:'query',type:'string',description:'Test1.i'},",
+					"{name:'D2',in:'header',type:'string',description:'Test1.j'}",
+				"],",
+				"responses:{",
+					"200: {description:'OK'},",
+					"201: {description:'Test1.l',headers:{bar:{description:'Test1.m',type:'string'}}}",
+				"}"
+			}
+			swagger=@MethodSwagger(
+				parameters={
+					"{name:'a',in:'path',type:'string',description:'Test1.d'},",
+					"{name:'b',in:'query',type:'string',description:'Test1.e'},",
+					"{in:'body',type:'string',description:'Test1.f'},",
+					"{name:'D',in:'header',type:'string',description:'Test1.g'},",
+					"{name:'a2',in:'path',type:'string',description:'Test1.h'},",
+					"{name:'b2',in:'query',type:'string',description:'Test1.i'},",
+					"{name:'D2',in:'header',type:'string',description:'Test1.j'}",
+				},
+				responses={
+					"200: {description:'OK'},",
+					"201: {description:'Test1.l',headers:{bar:{description:'Test1.m',type:'string'}}}",
+				}
+			)
+	 * 
+	 * 
+	 */
+	String[] value() default {};
+	
+	String[] summary() default {};
+	
+	String[] description() default {};
+
+	/**
+	 * Optional deprecated flag for the exposed API.
+	 * 
+	 * <p>
+	 * Used to populate the Swagger deprecated field.
+	 * 
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			deprecated=<jk>true</jk>
+	 * 		)
+	 * 	)
+	 * </p>
+	 * 
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		Corresponds to the swagger field <code>/paths/{path}/{method}/deprecated</code>.
+	 * </ul>
+	 */
+	String deprecated() default "";
+
+	/**
+	 * Optional external documentation information for the exposed API.
+	 * 
+	 * <p>
+	 * Used to populate the Swagger external documentation field.
+	 * 
+	 * <p>
+	 * A simplified JSON string with the following fields:
+	 * <p class='bcode'>
+	 * 	{
+	 * 		description: string,
+	 * 		url: string
+	 * 	}
+	 * </p>
+	 * 
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			<js>"{url:'http://juneau.apache.org'}"</js>
+	 * 		)
+	 * 	)
+	 * </p>
+	 * 
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		Supports <a class="doclink" href="../../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 
+	 * 		(e.g. <js>"$L{my.localized.variable}"</js>).
+	 * 	<li>
+	 * 		Corresponds to the swagger field <code>/paths/{path}/{method}/externalDocs</code>.
+	 * </ul>
+	 */
+	String[] externalDocs() default {};
+
+	/**
+	 * Optional parameter descriptions.
+	 * 
+	 * <p>
+	 * This annotation is provided for documentation purposes and is used to populate the method <js>"parameters"</js>
+	 * column on the Swagger page.
+	 * 
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		name=<jsf>POST</jsf>, path=<js>"/{a}"</js>,
+	 * 		description=<js>"This is my method."</js>,
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			parameters={
+	 * 				<ja>@Parameter</ja>(in=<js>"path"</js>, name=<js>"a"</js>, description=<js>"The 'a' attribute"</js>),
+	 * 				<ja>@Parameter</ja>(in=<js>"query"</js>, name=<js>"b"</js>, description=<js>"The 'b' parameter"</js>, required=<jk>true</jk>),
+	 * 				<ja>@Parameter</ja>(in=<js>"body"</js>, description=<js>"The HTTP content"</js>),
+	 * 				<ja>@Parameter</ja>(in=<js>"header"</js>, name=<js>"D"</js>, description=<js>"The 'D' header"</js>),
+	 * 			}
+	 * 		)
+	 * 	)
+	 * </p>
+	 * 
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		Supports <a class="doclink" href="../../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 
+	 * 		(e.g. <js>"$L{my.localized.variable}"</js>).
+	 * 	<li>
+	 * 		Corresponds to the swagger field <code>/paths/{path}/{method}/parameters</code>.
+	 * </ul>
+	 */
+	String[] parameters() default {};
+
+	/**
+	 * Optional output description.
+	 * 
+	 * <p>
+	 * This annotation is provided for documentation purposes and is used to populate the method <js>"responses"</js>
+	 * column on the Swagger page.
+	 * 
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		name=<jsf>GET</jsf>, path=<js>"/"</js>,
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			responses={
+	 * 				<ja>@Response</ja>(200),
+	 * 				<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>)
+	 * 					}
+	 * 				)
+	 * 			}
+	 * 		)
+	 * 	)
+	 * </p>
+	 * 
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		Supports <a class="doclink" href="../../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 
+	 * 		(e.g. <js>"$L{my.localized.variable}"</js>).
+	 * 	<li>
+	 * 		Corresponds to the swagger field <code>/paths/{path}/{method}/responses</code>.
+	 * </ul>
+	 */
+	String[] responses() default {};
+
+	/**
+	 * Optional tagging information for the exposed API.
+	 * 
+	 * <p>
+	 * Used to populate the Swagger tags field.
+	 * 
+	 * <p>
+	 * A comma-delimited list of tags for API documentation control.
+	 * <br>Tags can be used for logical grouping of operations by resources or any other qualifier.
+	 * 
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			tags=<js>"foo,bar"</js>
+	 * 		)
+	 * 	)
+	 * </p>
+	 * 
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		Supports <a class="doclink" href="../../../../../overview-summary.html#DefaultRestSvlVariables">initialization-time and request-time variables</a> 
+	 * 		(e.g. <js>"$L{my.localized.variable}"</js>).
+	 * 	<li>
+	 * 		Corresponds to the swagger field <code>/paths/{path}/{method}/tags</code>.
+	 * </ul>
+	 */
+	String[] tags() default "";
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/ResourceSwagger.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/ResourceSwagger.java
new file mode 100644
index 0000000..c13cdac
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/ResourceSwagger.java
@@ -0,0 +1,356 @@
+// ***************************************************************************************************************************
+// * 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.annotation;
+
+import org.apache.juneau.config.vars.*;
+import org.apache.juneau.rest.vars.*;
+import org.apache.juneau.svl.vars.*;
+
+/**
+ * Extended annotation for {@link RestResource#swagger() @RestResource.swagger()}.
+ * 
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../overview-summary.html#juneau-rest-server.OptionsPages">Overview &gt; juneau-rest-server &gt; OPTIONS pages</a>
+ * </ul>
+ */
+public @interface ResourceSwagger {
+	
+	String[] value() default {};
+	
+	String[] title() default {};
+	
+	String[] description() default {};
+
+	/**
+	 * Optional contact information for the exposed API.
+	 * 
+	 * <p>
+	 * It is used to populate the Swagger contact field and to display on HTML pages.
+	 * 
+	 * <p>
+	 * A simplified JSON string with the following fields:
+	 * <p class='bcode'>
+	 * 	{
+	 * 		name: string,
+	 * 		url: string,
+	 * 		email: string
+	 * 	}
+	 * </p>
+	 * 
+	 * <p>
+	 * The default value pulls the description from the <code>contact</code> entry in the servlet resource bundle.
+	 * (e.g. <js>"contact = {name:'John Smith',email:'john.smith@foo.bar'}"</js> or
+	 * <js>"MyServlet.contact = {name:'John Smith',email:'john.smith@foo.bar'}"</js>).
+	 * 
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			contact=<js>"{name:'John Smith',email:'john.smith@foo.bar'}"</js>
+	 * 		)
+	 * 	)
+	 * </p>
+	swagger={
+		"info: {",
+			"contact:{name:'Juneau Developer',email:'dev@juneau.apache.org'},",
+			"license:{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
+			"version:'2.0',",
+			"termsOfService:'You are on your own.'",
+		"},",
+		"externalDocs:{description:'Apache Juneau',url:'http://juneau.apache.org'}"
+	}
+	swagger=@ResourceSwagger(
+		contact="name:'Juneau Developer',email:'dev@juneau.apache.org'",
+		license="name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'",
+		version="2.0",
+		termsOfService="You are on your own.",
+		externalDocs="description:'Apache Juneau',url:'http://juneau.apache.org'}"
+	)
+	swagger=@ResourceSwagger("$F{PetStoreResource.json}"),
+	 * 
+	 * <p>
+	 * Value can contain any of the following variables:  
+	 * {@link ConfigVar $C} 
+	 * {@link CoalesceVar $CO}
+	 * {@link EnvVariablesVar $E} 
+	 * {@link FileVar $F} 
+	 * {@link ServletInitParamVar $I},
+	 * {@link IfVar $IF}
+	 * {@link LocalizationVar $L}
+	 * {@link RequestAttributeVar $RA} 
+	 * {@link RequestFormDataVar $RF} 
+	 * {@link RequestHeaderVar $RH} 
+	 * {@link RequestPathVar $RP} 
+	 * {@link RequestQueryVar $RQ} 
+	 * {@link RequestVar $R} 
+	 * {@link SystemPropertiesVar $S}
+	 * {@link SerializedRequestAttrVar $SA}
+	 * {@link SwitchVar $SW}
+	 * {@link UrlVar $U}
+	 * {@link UrlEncodeVar $UE}
+	 * {@link WidgetVar $W}
+	 * 
+	 * <p>
+	 * Corresponds to the swagger field <code>/info/contact</code>.
+	 */
+	String[] contact() default {};
+
+	/**
+	 * Optional external documentation information for the exposed API.
+	 * 
+	 * <p>
+	 * It is used to populate the Swagger external documentation field and to display on HTML pages.
+	 * 
+	 * <p>
+	 * A simplified JSON string with the following fields:
+	 * <p class='bcode'>
+	 * 	{
+	 * 		description: string,
+	 * 		url: string
+	 * 	}
+	 * </p>
+	 * 
+	 * <p>
+	 * The default value pulls the description from the <code>externalDocs</code> entry in the servlet resource bundle.
+	 * (e.g. <js>"externalDocs = {url:'http://juneau.apache.org'}"</js> or
+	 * <js>"MyServlet.externalDocs = {url:'http://juneau.apache.org'}"</js>).
+	 * 
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			externalDocs=<js>"{url:'http://juneau.apache.org'}"</js>
+	 * 		)
+	 * 	)
+	 * </p>
+	 * 
+	 * <p>
+	 * Value can contain any of the following variables:  
+	 * {@link ConfigVar $C} 
+	 * {@link CoalesceVar $CO}
+	 * {@link EnvVariablesVar $E} 
+	 * {@link FileVar $F} 
+	 * {@link ServletInitParamVar $I},
+	 * {@link IfVar $IF}
+	 * {@link LocalizationVar $L}
+	 * {@link RequestAttributeVar $RA} 
+	 * {@link RequestFormDataVar $RF} 
+	 * {@link RequestHeaderVar $RH} 
+	 * {@link RequestPathVar $RP} 
+	 * {@link RequestQueryVar $RQ} 
+	 * {@link RequestVar $R} 
+	 * {@link SystemPropertiesVar $S}
+	 * {@link SerializedRequestAttrVar $SA}
+	 * {@link SwitchVar $SW}
+	 * {@link UrlVar $U}
+	 * {@link UrlEncodeVar $UE}
+	 * {@link WidgetVar $W}
+	 * 
+	 * <p>
+	 * Corresponds to the swagger field <code>/tags</code>.
+	 */
+	String[] externalDocs() default {};
+
+	/**
+	 * Optional license information for the exposed API.
+	 * 
+	 * <p>
+	 * It is used to populate the Swagger license field and to display on HTML pages.
+	 * 
+	 * <p>
+	 * A simplified JSON string with the following fields:
+	 * <p class='bcode'>
+	 * 	{
+	 * 		name: string,
+	 * 		url: string
+	 * 	}
+	 * </p>
+	 * 
+	 * <p>
+	 * The default value pulls the description from the <code>license</code> entry in the servlet resource bundle.
+	 * (e.g. <js>"license = {name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'}"</js> or
+	 * <js>"MyServlet.license = {name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'}"</js>).
+	 * 
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			license=<js>"{name:'Apache 2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'}"</js>
+	 * 		)
+	 * 	)
+	 * </p>
+	 * 
+	 * <p>
+	 * Value can contain any of the following variables:  
+	 * {@link ConfigVar $C} 
+	 * {@link CoalesceVar $CO}
+	 * {@link EnvVariablesVar $E} 
+	 * {@link FileVar $F} 
+	 * {@link ServletInitParamVar $I},
+	 * {@link IfVar $IF}
+	 * {@link LocalizationVar $L}
+	 * {@link RequestAttributeVar $RA} 
+	 * {@link RequestFormDataVar $RF} 
+	 * {@link RequestHeaderVar $RH} 
+	 * {@link RequestPathVar $RP} 
+	 * {@link RequestQueryVar $RQ} 
+	 * {@link RequestVar $R} 
+	 * {@link SystemPropertiesVar $S}
+	 * {@link SerializedRequestAttrVar $SA}
+	 * {@link SwitchVar $SW}
+	 * {@link UrlVar $U}
+	 * {@link UrlEncodeVar $UE}
+	 * {@link WidgetVar $W}
+	 * 
+	 * <p>
+	 * Corresponds to the swagger field <code>/info/license</code>.
+	 */
+	String[] license() default {};
+
+	/**
+	 * Optional tagging information for the exposed API.
+	 * 
+	 * <p>
+	 * It is used to populate the Swagger tags field and to display on HTML pages.
+	 * 
+	 * <p>
+	 * A simplified JSON string with the following fields:
+	 * <p class='bcode'>
+	 * 	[
+	 * 		{
+	 * 			name: string,
+	 * 			description: string,
+	 * 			externalDocs: {
+	 * 				description: string,
+	 * 				url: string
+	 * 			}
+	 * 		}
+	 * 	]
+	 * </p>
+	 * 
+	 * <p>
+	 * The default value pulls the description from the <code>tags</code> entry in the servlet resource bundle.
+	 * (e.g. <js>"tags = [{name:'Foo',description:'Foobar'}]"</js> or
+	 * <js>"MyServlet.tags = [{name:'Foo',description:'Foobar'}]"</js>).
+	 * 
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<ja>@RestResource</ja>(
+	 * 		swagger=<ja>@MethodSwagger</ja>(
+	 * 			tags=<js>"[{name:'Foo',description:'Foobar'}]"</js>
+	 * 		)
+	 * 	)
+	 * </p>
+	 * 
+	 * <p>
+	 * Value can contain any of the following variables:  
+	 * {@link ConfigVar $C} 
+	 * {@link CoalesceVar $CO}
+	 * {@link EnvVariablesVar $E} 
+	 * {@link FileVar $F} 
+	 * {@link ServletInitParamVar $I},
+	 * {@link IfVar $IF}
+	 * {@link LocalizationVar $L}
+	 * {@link RequestAttributeVar $RA} 
+	 * {@link RequestFormDataVar $RF} 
+	 * {@link RequestHeaderVar $RH} 
+	 * {@link RequestPathVar $RP} 
+	 * {@link RequestQueryVar $RQ} 
+	 * {@link RequestVar $R} 
+	 * {@link SystemPropertiesVar $S}
+	 * {@link SerializedRequestAttrVar $SA}
+	 * {@link SwitchVar $SW}
+	 * {@link UrlVar $U}
+	 * {@link UrlEncodeVar $UE}
+	 * {@link WidgetVar $W}
+	 * 
+	 * <p>
+	 * Corresponds to the swagger field <code>/tags</code>.
+	 */
+	String[] tags() default {};
+	
+	/**
+	 * Optional servlet terms-of-service for this API.
+	 * 
+	 * <p>
+	 * It is used to populate the Swagger terms-of-service field.
+	 * 
+	 * <p>
+	 * The default value pulls the description from the <code>termsOfService</code> entry in the servlet resource bundle.
+	 * (e.g. <js>"termsOfService = foo"</js> or <js>"MyServlet.termsOfService = foo"</js>).
+	 * 
+	 * <p>
+	 * Value can contain any of the following variables:  
+	 * {@link ConfigVar $C} 
+	 * {@link CoalesceVar $CO}
+	 * {@link EnvVariablesVar $E} 
+	 * {@link FileVar $F} 
+	 * {@link ServletInitParamVar $I},
+	 * {@link IfVar $IF}
+	 * {@link LocalizationVar $L}
+	 * {@link RequestAttributeVar $RA} 
+	 * {@link RequestFormDataVar $RF} 
+	 * {@link RequestHeaderVar $RH} 
+	 * {@link RequestPathVar $RP} 
+	 * {@link RequestQueryVar $RQ} 
+	 * {@link RequestVar $R} 
+	 * {@link SystemPropertiesVar $S}
+	 * {@link SerializedRequestAttrVar $SA}
+	 * {@link SwitchVar $SW}
+	 * {@link UrlVar $U}
+	 * {@link UrlEncodeVar $UE}
+	 * {@link WidgetVar $W}
+	 * 
+	 * <p>
+	 * Corresponds to the swagger field <code>/info/termsOfService</code>.
+	 */
+	String[] termsOfService() default {};
+
+	/**
+	 * Provides the version of the application API (not to be confused with the specification version).
+	 * 
+	 * <p>
+	 * It is used to populate the Swagger version field and to display on HTML pages.
+	 * 
+	 * <p>
+	 * The default value pulls the description from the <code>version</code> entry in the servlet resource bundle.
+	 * (e.g. <js>"version = 2.0"</js> or <js>"MyServlet.version = 2.0"</js>).
+	 * 
+	 * <p>
+	 * Value can contain any of the following variables:  
+	 * {@link ConfigVar $C} 
+	 * {@link CoalesceVar $CO}
+	 * {@link EnvVariablesVar $E} 
+	 * {@link FileVar $F} 
+	 * {@link ServletInitParamVar $I},
+	 * {@link IfVar $IF}
+	 * {@link LocalizationVar $L}
+	 * {@link RequestAttributeVar $RA} 
+	 * {@link RequestFormDataVar $RF} 
+	 * {@link RequestHeaderVar $RH} 
+	 * {@link RequestPathVar $RP} 
+	 * {@link RequestQueryVar $RQ} 
+	 * {@link RequestVar $R} 
+	 * {@link SystemPropertiesVar $S}
+	 * {@link SerializedRequestAttrVar $SA}
+	 * {@link SwitchVar $SW}
+	 * {@link UrlVar $U}
+	 * {@link UrlEncodeVar $UE}
+	 * {@link WidgetVar $W}
+	 * 
+	 * <p>
+	 * Corresponds to the swagger field <code>/info/version</code>.
+	 */
+	String version() default "";
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
index 6d34b0c..cae82b4 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
@@ -766,11 +766,12 @@ public @interface RestMethod {
 	 * 		}
 	 * 	)
 	 * </p>
+	 * {@link TODO}
 	 * 
 	 * <h5 class='section'>See Also:</h5>
 	 * <ul>
 	 * 	<li class='jm'>{@link RestInfoProvider#getSwagger(RestRequest)}
 	 * </ul>
 	 */
-	String[] swagger() default {};
+	MethodSwagger swagger() default @MethodSwagger;
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
index c2b3470..1263201 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
@@ -859,13 +859,14 @@ public @interface RestResource {
 	 * 		}
 	 * 	)
 	 * </p>
+	 * {@link TODO}
 	 * 
 	 * <h5 class='section'>See Also:</h5>
 	 * <ul>
 	 * 	<li class='jm'>{@link RestInfoProvider#getSwagger(RestRequest)}
 	 * </ul>
 	 */
-	String[] swagger() default {};
+	ResourceSwagger swagger() default @ResourceSwagger;
 
 	/**
 	 * Optional servlet title.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
index df36c7e..7c86e38 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
@@ -165,8 +165,8 @@ public abstract class RemoteableServlet extends BasicRestServlet {
 				"<h5>Method:  $RP{javaMethod}</h5>"
 			}
 		),
-		swagger= {
-			"parameters: [",
+		swagger=@MethodSwagger(
+			parameters= {
 				"{",
 					"in: 'body',",
 					"description: 'Serialized array of Java objects',",
@@ -176,12 +176,12 @@ public abstract class RemoteableServlet extends BasicRestServlet {
 					"x-examples: {",
 						"'application/json+lax': '[\\'foo\\', 123, true]'",
 					"}",
-				"}",
-			"],",
-			"responses:{",
+				"}"
+			},
+			responses= {
 				"200:{ description:'The return object serialized', schema:{type:'any'},'x-example':{foo:123} }",
-			"}"
-		}
+			}
+		)
 	)
 	public Object invoke(
 			Reader r,
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java
index 0a5245c..9cad6ea 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java
@@ -19,6 +19,7 @@ import org.apache.juneau.http.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.exception.*;
+import org.apache.juneau.rest.util.*;
 import org.apache.juneau.serializer.*;
 
 /**
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/BoundedServletInputStream.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/BoundedServletInputStream.java
new file mode 100644
index 0000000..0792fe4
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/BoundedServletInputStream.java
@@ -0,0 +1,144 @@
+// ***************************************************************************************************************************
+// * 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.util;
+
+import java.io.*;
+
+import javax.servlet.*;
+
+/**
+ * ServletInputStream wrapper around a normal input stream with support for limiting input.
+ */
+public final class BoundedServletInputStream extends ServletInputStream {
+
+	private final InputStream is;
+	private final ServletInputStream sis;
+	private long remain;
+
+	/**
+	 * Wraps the specified input stream.
+	 * 
+	 * @param is The input stream to wrap.
+	 * @param max The maximum number of bytes to read from the stream.
+	 */
+	public BoundedServletInputStream(InputStream is, long max) {
+		this.is = is;
+		this.sis = null;
+		this.remain = max;
+	}
+
+	/**
+	 * Wraps the specified input stream.
+	 * 
+	 * @param sis The input stream to wrap.
+	 * @param max The maximum number of bytes to read from the stream.
+	 */
+	public BoundedServletInputStream(ServletInputStream sis, long max) {
+		this.sis = sis;
+		this.is = sis;
+		this.remain = max;
+	}
+
+	/**
+	 * Wraps the specified byte array.
+	 * 
+	 * @param b The byte contents of the stream.
+	 */
+	public BoundedServletInputStream(byte[] b) {
+		this(new ByteArrayInputStream(b), Long.MAX_VALUE);
+	}
+
+	@Override /* InputStream */
+	public final int read() throws IOException {
+		decrement();
+		return is.read();
+	}
+
+	@Override /* InputStream */
+	public int read(byte[] b) throws IOException {
+		return read(b, 0, b.length);
+	}
+
+	@Override /* InputStream */
+	public int read(final byte[] b, final int off, final int len) throws IOException {
+		long numBytes = Math.min(len, remain);
+		int r = is.read(b, off, (int) numBytes);
+		if (r == -1) 
+			return -1;
+		decrement(numBytes);
+		return r;
+	}
+
+	@Override /* InputStream */
+	public long skip(final long n) throws IOException {
+		long toSkip = Math.min(n, remain);
+		long r = is.skip(toSkip);
+		decrement(r);
+		return r;
+	}
+
+	@Override /* InputStream */
+	public int available() throws IOException {
+		if (remain <= 0)
+			return 0;
+		return is.available();
+	}
+
+	@Override /* InputStream */
+	public synchronized void reset() throws IOException {
+		is.reset();
+	}
+
+	@Override /* InputStream */
+	public synchronized void mark(int limit) {
+		is.mark(limit);
+	}
+
+	@Override /* InputStream */
+	public boolean markSupported() {
+		return is.markSupported();
+	}
+
+	@Override /* InputStream */
+	public final void close() throws IOException {
+		is.close();
+	}
+
+	@Override /* ServletInputStream */
+	public boolean isFinished() {
+		return sis == null ? false : sis.isFinished();
+	}
+
+	@Override /* ServletInputStream */
+	public boolean isReady() {
+		return sis == null ? true : sis.isReady();
+	}
+
+	@Override /* ServletInputStream */
+	public void setReadListener(ReadListener arg0) {
+		if (sis != null)
+			sis.setReadListener(arg0);
+	}
+	
+	private void decrement() throws IOException {
+		remain--;
+		if (remain < 0)
+			throw new IOException("Input limit exceeded.  See @RestResource.maxInput().");
+	}
+
+	private void decrement(long count) throws IOException {
+		remain -= count;
+		if (remain < 0)
+			throw new IOException("Input limit exceeded.  See @RestResource.maxInput().");
+	}
+}
\ No newline at end of file
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishablePrintWriter.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/FinishablePrintWriter.java
similarity index 87%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishablePrintWriter.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/FinishablePrintWriter.java
index 4af84f4..6d317c5 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishablePrintWriter.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/FinishablePrintWriter.java
@@ -10,7 +10,7 @@
 // * "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;
+package org.apache.juneau.rest.util;
 
 import java.io.*;
 
@@ -23,7 +23,14 @@ public class FinishablePrintWriter extends PrintWriter implements Finishable {
 
 	final Finishable f;
 
-	FinishablePrintWriter(OutputStream out, String characterEncoding) throws IOException {
+	/**
+	 * Constructor.
+	 * 
+	 * @param out The wrapped output stream.
+	 * @param characterEncoding The character encoding of the output stream.
+	 * @throws IOException
+	 */
+	public FinishablePrintWriter(OutputStream out, String characterEncoding) throws IOException {
 		super(new OutputStreamWriter(out, characterEncoding));
 		f = (out instanceof Finishable ? (Finishable)out : null);
 	}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishableServletOutputStream.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/FinishableServletOutputStream.java
similarity index 94%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishableServletOutputStream.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/FinishableServletOutputStream.java
index 87e2044..d1417ee 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/FinishableServletOutputStream.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/FinishableServletOutputStream.java
@@ -10,7 +10,7 @@
 // * "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;
+package org.apache.juneau.rest.util;
 
 import java.io.*;
 
@@ -27,7 +27,12 @@ public class FinishableServletOutputStream extends ServletOutputStream implement
 	final ServletOutputStream sos;
 	final Finishable f;
 	
-	FinishableServletOutputStream(OutputStream os) {
+	/**
+	 * Constructor.
+	 * 
+	 * @param os The wrapped output stream.
+	 */
+	public FinishableServletOutputStream(OutputStream os) {
 		this.os = os;
 		this.sos = (os instanceof ServletOutputStream ? (ServletOutputStream)os : null);
 		this.f = (os instanceof Finishable ? (Finishable)os : null);
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/MockHttpServletRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/MockHttpServletRequest.java
new file mode 100644
index 0000000..40c9dec
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/MockHttpServletRequest.java
@@ -0,0 +1,799 @@
+// ***************************************************************************************************************************
+// * 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.util;
+
+import java.io.*;
+import java.security.*;
+import java.util.*;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+import org.apache.juneau.internal.*;
+
+/**
+ * An implementation of {@link HttpServletRequest} for testing purposes.
+ */
+public class MockHttpServletRequest implements HttpServletRequest {
+	
+	private String method = "GET";
+	private Map<String,String[]> parameterMap = new LinkedHashMap<>();
+	private Map<String,String[]> headerMap = new LinkedHashMap<>();	
+	private Map<String,Object> attributeMap = new LinkedHashMap<>();
+	private String characterEncoding = "UTF-8";
+	private byte[] body;
+	private String protocol = "HTTP/1.1";
+	private String scheme = "http";
+	private String serverName = "localhost";
+	private int serverPort = 8080;
+	private String remoteAddr = "";
+	private String remoteHost = "";
+	private Locale locale = Locale.ENGLISH;
+	private String realPath;
+	private int remotePort;
+	private String localName;
+	private String localAddr;
+	private int localPort;
+	private RequestDispatcher requestDispatcher;
+	private ServletContext servletContext;
+	private DispatcherType dispatcherType;
+	private String authType;
+	private Cookie[] cookies;
+	private String pathInfo;
+	private String pathTranslated;
+	private String contextPath;
+	private String queryString;
+	private String remoteUser;
+	private Principal userPrincipal;
+	private String requestedSessionId;
+	private String requestURI;
+	private String servletPath;
+	private HttpSession httpSession;
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The method name for this request.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest method(String value) {
+		this.method = value;
+		return this;
+	}
+
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param name Query parameter name. 
+	 * @param value Query parameter values.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest param(String name, String[] value) {
+		this.parameterMap.put(name, value);
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param name Query parameter name. 
+	 * @param value Query parameter value.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest param(String name, String value) {
+		this.parameterMap.put(name, new String[] {value});
+		return this;
+	}
+
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param name Header name. 
+	 * @param value Header value.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest header(String name, String value) {
+		this.headerMap.put(name, new String[] {value});
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param name Request attribute name. 
+	 * @param value Request attribute value.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest attribute(String name, Object value) {
+		this.attributeMap.put(name, value);
+		return this;
+	}
+
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The body of the request.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest body(Object value) {
+		try {
+			if (value instanceof byte[])
+				this.body = (byte[])value;
+			if (value instanceof Reader)
+				this.body = IOUtils.read((Reader)value).getBytes();
+			if (value instanceof InputStream)
+				this.body = IOUtils.readBytes((InputStream)value, 1024);
+			if (value instanceof CharSequence)
+				this.body = ((CharSequence)value).toString().getBytes();
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+		return this;
+	}
+
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The character encoding.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest characterEncoding(String value) {
+		this.characterEncoding = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The protocol.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest protocol(String value) {
+		this.protocol = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The scheme.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest scheme(String value) {
+		this.scheme = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The server name.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest serverName(String value) {
+		this.serverName = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The server port.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest serverPort(int value) {
+		this.serverPort = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The remote address.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest remoteAddr(String value) {
+		this.remoteAddr = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The remote port.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest remoteHost(String value) {
+		this.remoteHost = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The locale.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest locale(Locale value) {
+		this.locale = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The real path.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest realPath(String value) {
+		this.realPath = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The remote port.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest remotePort(int value) {
+		this.remotePort = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The local name.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest localName(String value) {
+		this.localName = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The local address.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest localAddr(String value) {
+		this.localAddr = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The local port.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest localPort(int value) {
+		this.localPort = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The request dispatcher.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest requestDispatcher(RequestDispatcher value) {
+		this.requestDispatcher = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The servlet context.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest servletContext(ServletContext value) {
+		this.servletContext = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The dispatcher type.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest dispatcherType(DispatcherType value) {
+		this.dispatcherType = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The auth type.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest authType(String value) {
+		this.authType = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The cookies.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest cookies(Cookie[] value) {
+		this.cookies = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The path info.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest pathInfo(String value) {
+		this.pathInfo = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The path translated.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest pathTranslated(String value) {
+		this.pathTranslated = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The context path.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest contextPath(String value) {
+		this.contextPath = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The query string.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest queryString(String value) {
+		this.queryString = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The remote user.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest remoteUser(String value) {
+		this.remoteUser = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The user principal.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest userPrincipal(Principal value) {
+		this.userPrincipal = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The requested session ID.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest requestedSessionId(String value) {
+		this.requestedSessionId = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The request URI.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest requestURI(String value) {
+		this.requestURI = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The servlet path.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest servletPath(String value) {
+		this.servletPath = value;
+		return this;
+	}
+	
+	/**
+	 * Fluent setter.
+	 * 
+	 * @param value The HTTP session.
+	 * @return This object (for method chaining).
+	 */
+	public MockHttpServletRequest httpSession(HttpSession value) {
+		this.httpSession = value;
+		return this;
+	}
+	
+	@Override /* HttpServletRequest */
+	public Object getAttribute(String name) {
+		return attributeMap.get(name);
+	}
+
+	@Override /* HttpServletRequest */
+	public Enumeration<String> getAttributeNames() {
+		return Collections.enumeration(attributeMap.keySet());
+	}
+
+	@Override /* HttpServletRequest */
+	public String getCharacterEncoding() {
+		return characterEncoding;
+	}
+
+	@Override /* HttpServletRequest */
+	public void setCharacterEncoding(String characterEncoding) throws UnsupportedEncodingException {
+		this.characterEncoding = characterEncoding;
+	}
+
+	@Override /* HttpServletRequest */
+	public int getContentLength() {
+		return body == null ? 0 : body.length;
+	}
+
+	@Override /* HttpServletRequest */
+	public long getContentLengthLong() {
+		return body == null ? 0 : body.length;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getContentType() {
+		return getHeader("Content-Type");
+	}
+
+	@Override /* HttpServletRequest */
+	public ServletInputStream getInputStream() throws IOException {
+		return new BoundedServletInputStream(new ByteArrayInputStream(body), Integer.MAX_VALUE);
+	}
+
+	@Override /* HttpServletRequest */
+	public String getParameter(String name) {
+		String[] s = parameterMap.get(name);
+		return s == null || s.length == 0 ? null : s[0];
+	}
+
+	@Override /* HttpServletRequest */
+	public Enumeration<String> getParameterNames() {
+		return Collections.enumeration(new ArrayList<>(parameterMap.keySet()));
+	}
+
+	@Override /* HttpServletRequest */
+	public String[] getParameterValues(String name) {
+		return parameterMap.get(name);
+	}
+
+	@Override /* HttpServletRequest */
+	public Map<String,String[]> getParameterMap() {
+		return parameterMap;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getProtocol() {
+		return protocol;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getScheme() {
+		return scheme;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getServerName() {
+		return serverName;
+	}
+
+	@Override /* HttpServletRequest */
+	public int getServerPort() {
+		return serverPort;
+	}
+
+	@Override /* HttpServletRequest */
+	public BufferedReader getReader() throws IOException {
+		return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(body), characterEncoding));
+	}
+
+	@Override /* HttpServletRequest */
+	public String getRemoteAddr() {
+		return remoteAddr;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getRemoteHost() {
+		return remoteHost;
+	}
+
+	@Override /* HttpServletRequest */
+	public void setAttribute(String name, Object o) {
+		this.attributeMap.put(name, o);
+	}
+
+	@Override /* HttpServletRequest */
+	public void removeAttribute(String name) {
+		this.attributeMap.remove(name);
+	}
+
+	@Override /* HttpServletRequest */
+	public Locale getLocale() {
+		return locale;
+	}
+
+	@Override /* HttpServletRequest */
+	public Enumeration<Locale> getLocales() {
+		return Collections.enumeration(Arrays.asList(locale));
+	}
+
+	@Override /* HttpServletRequest */
+	public boolean isSecure() {
+		return false;
+	}
+
+	@Override /* HttpServletRequest */
+	public RequestDispatcher getRequestDispatcher(String path) {
+		return requestDispatcher;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getRealPath(String path) {
+		return realPath;
+	}
+
+	@Override /* HttpServletRequest */
+	public int getRemotePort() {
+		return remotePort;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getLocalName() {
+		return localName;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getLocalAddr() {
+		return localAddr;
+	}
+
+	@Override /* HttpServletRequest */
+	public int getLocalPort() {
+		return localPort;
+	}
+
+	@Override /* HttpServletRequest */
+	public ServletContext getServletContext() {
+		return servletContext;
+	}
+
+	@Override /* HttpServletRequest */
+	public AsyncContext startAsync() throws IllegalStateException {
+		return null;
+	}
+
+	@Override /* HttpServletRequest */
+	public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
+		return null;
+	}
+
+	@Override /* HttpServletRequest */
+	public boolean isAsyncStarted() {
+		return false;
+	}
+
+	@Override /* HttpServletRequest */
+	public boolean isAsyncSupported() {
+		return false;
+	}
+
+	@Override /* HttpServletRequest */
+	public AsyncContext getAsyncContext() {
+		return null;
+	}
+
+	@Override /* HttpServletRequest */
+	public DispatcherType getDispatcherType() {
+		return dispatcherType;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getAuthType() {
+		return authType;
+	}
+
+	@Override /* HttpServletRequest */
+	public Cookie[] getCookies() {
+		return cookies;
+	}
+
+	@Override /* HttpServletRequest */
+	public long getDateHeader(String name) {
+		String s = getHeader(name);
+		return s == null ? 0 : org.apache.juneau.http.Date.forString(s).asDate().getTime();
+	}
+
+	@Override /* HttpServletRequest */
+	public String getHeader(String name) {
+		String[] s = headerMap.get(name);
+		return s == null || s.length == 0 ? null : s[0];
+	}
+
+	@Override /* HttpServletRequest */
+	public Enumeration<String> getHeaders(String name) {
+		String[] s = headerMap.get(name);
+		return Collections.enumeration(Arrays.asList(s == null ? new String[0] : s));
+	}
+
+	@Override /* HttpServletRequest */
+	public Enumeration<String> getHeaderNames() {
+		return Collections.enumeration(headerMap.keySet());
+	}
+
+	@Override /* HttpServletRequest */
+	public int getIntHeader(String name) {
+		String s = getHeader(name);
+		return s == null || s.isEmpty() ? 0 : Integer.parseInt(s);
+	}
+
+	@Override /* HttpServletRequest */
+	public String getMethod() {
+		return method;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getPathInfo() {
+		return pathInfo;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getPathTranslated() {
+		return pathTranslated;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getContextPath() {
+		return contextPath;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getQueryString() {
+		return queryString;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getRemoteUser() {
+		return remoteUser;
+	}
+
+	@Override /* HttpServletRequest */
+	public boolean isUserInRole(String role) {
+		return false;
+	}
+
+	@Override /* HttpServletRequest */
+	public Principal getUserPrincipal() {
+		return userPrincipal;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getRequestedSessionId() {
+		return requestedSessionId;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getRequestURI() {
+		return requestURI;
+	}
+
+	@Override /* HttpServletRequest */
+	public StringBuffer getRequestURL() {
+		return new StringBuffer(requestURI);
+	}
+
+	@Override /* HttpServletRequest */
+	public String getServletPath() {
+		return servletPath;
+	}
+
+	@Override /* HttpServletRequest */
+	public HttpSession getSession(boolean create) {
+		return httpSession;
+	}
+
+	@Override /* HttpServletRequest */
+	public HttpSession getSession() {
+		return httpSession;
+	}
+
+	@Override /* HttpServletRequest */
+	public String changeSessionId() {
+		return null;
+	}
+
+	@Override /* HttpServletRequest */
+	public boolean isRequestedSessionIdValid() {
+		return false;
+	}
+
+	@Override /* HttpServletRequest */
+	public boolean isRequestedSessionIdFromCookie() {
+		return false;
+	}
+
+	@Override /* HttpServletRequest */
+	public boolean isRequestedSessionIdFromURL() {
+		return false;
+	}
+
+	@Override /* HttpServletRequest */
+	public boolean isRequestedSessionIdFromUrl() {
+		return false;
+	}
+
+	@Override /* HttpServletRequest */
+	public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
+		return false;
+	}
+
+	@Override /* HttpServletRequest */
+	public void login(String username, String password) throws ServletException {
+	}
+
+	@Override /* HttpServletRequest */
+	public void logout() throws ServletException {
+	}
+
+	@Override /* HttpServletRequest */
+	public Collection<Part> getParts() throws IOException, ServletException {
+		return null;
+	}
+
+	@Override /* HttpServletRequest */
+	public Part getPart(String name) throws IOException, ServletException {
+		return null;
+	}
+
+	@Override /* HttpServletRequest */
+	public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
+		return null;
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/MockHttpServletResponse.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/MockHttpServletResponse.java
new file mode 100644
index 0000000..5e5fc60
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/MockHttpServletResponse.java
@@ -0,0 +1,240 @@
+// ***************************************************************************************************************************
+// * 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.util;
+
+import java.io.*;
+import java.util.*;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+import org.apache.juneau.internal.*;
+
+/**
+ * An implementation of {@link HttpServletResponse} for testing purposes.
+ */
+public class MockHttpServletResponse implements HttpServletResponse {
+
+	private String characterEncoding = "UTF-8";
+	private ByteArrayOutputStream baos = new ByteArrayOutputStream();
+	private long contentLength = 0;
+	private int bufferSize = 0;
+	private Locale locale;
+	private int sc;
+	private String msg;
+	private Map<String,String[]> headerMap = new LinkedHashMap<>();
+	
+	/**
+	 * Returns the content length.
+	 * 
+	 * @return The content length.
+	 */
+	public long getContentLength() {
+		return contentLength;
+	}
+	
+	/**
+	 * Returns the response message.
+	 * 
+	 * @return The response message.
+	 */
+	public String getMessage() {
+		return msg;
+	}
+
+	@Override /* HttpServletResponse */
+	public String getCharacterEncoding() {
+		return characterEncoding ;
+	}
+
+	@Override /* HttpServletResponse */
+	public String getContentType() {
+		return getHeader("Content-Type");
+	}
+
+	@Override /* HttpServletResponse */
+	public ServletOutputStream getOutputStream() throws IOException {
+		return new FinishableServletOutputStream(baos); 
+	}
+
+	@Override /* HttpServletResponse */
+	public PrintWriter getWriter() throws IOException {
+		return new PrintWriter(new OutputStreamWriter(getOutputStream(), characterEncoding));
+	}
+
+	@Override /* HttpServletResponse */
+	public void setCharacterEncoding(String charset) {
+		this.characterEncoding = charset;
+	}
+
+	@Override /* HttpServletResponse */
+	public void setContentLength(int len) {
+		this.contentLength = len;
+	}
+
+	@Override /* HttpServletResponse */
+	public void setContentLengthLong(long len) {
+		this.contentLength = len;
+	}
+
+	@Override /* HttpServletResponse */
+	public void setContentType(String type) {
+		setHeader("Content-Type", type);
+	}
+
+	@Override /* HttpServletResponse */
+	public void setBufferSize(int size) {
+		this.bufferSize = size;
+	}
+
+	@Override /* HttpServletResponse */
+	public int getBufferSize() {
+		return bufferSize;
+	}
+
+	@Override /* HttpServletResponse */
+	public void flushBuffer() throws IOException {
+	}
+
+	@Override /* HttpServletResponse */
+	public void resetBuffer() {
+	}
+
+	@Override /* HttpServletResponse */
+	public boolean isCommitted() {
+		return false;
+	}
+
+	@Override /* HttpServletResponse */
+	public void reset() {
+	}
+
+	@Override /* HttpServletResponse */
+	public void setLocale(Locale loc) {
+		this.locale = loc;
+	}
+
+	@Override /* HttpServletResponse */
+	public Locale getLocale() {
+		return locale;
+	}
+
+	@Override /* HttpServletResponse */
+	public void addCookie(Cookie cookie) {
+	}
+
+	@Override /* HttpServletResponse */
+	public boolean containsHeader(String name) {
+		return getHeader(name) != null;
+	}
+
+	@Override /* HttpServletResponse */
+	public String encodeURL(String url) {
+		return null;
+	}
+
+	@Override /* HttpServletResponse */
+	public String encodeRedirectURL(String url) {
+		return null;
+	}
+
+	@Override /* HttpServletResponse */
+	public String encodeUrl(String url) {
+		return null;
+	}
+
+	@Override /* HttpServletResponse */
+	public String encodeRedirectUrl(String url) {
+		return null;
+	}
+
+	@Override /* HttpServletResponse */
+	public void sendError(int sc, String msg) throws IOException {
+		this.sc = sc;
+		this.msg = msg;
+	}
+
+	@Override /* HttpServletResponse */
+	public void sendError(int sc) throws IOException {
+		this.sc = sc;
+	}
+
+	@Override /* HttpServletResponse */
+	public void sendRedirect(String location) throws IOException {
+		this.sc = 302;
+		headerMap.put("Location", new String[] {location});
+	}
+
+	@Override /* HttpServletResponse */
+	public void setDateHeader(String name, long date) {
+		headerMap.put(name, new String[] {DateUtils.formatDate(new Date(date), DateUtils.PATTERN_RFC1123)});
+	}
+
+	@Override /* HttpServletResponse */
+	public void addDateHeader(String name, long date) {
+		headerMap.put(name, new String[] {DateUtils.formatDate(new Date(date), DateUtils.PATTERN_RFC1123)});
+	}
+
+	@Override /* HttpServletResponse */
+	public void setHeader(String name, String value) {
+		headerMap.put(name, new String[] {value});
+	}
+
+	@Override /* HttpServletResponse */
+	public void addHeader(String name, String value) {
+		headerMap.put(name, new String[] {value});
+	}
+
+	@Override /* HttpServletResponse */
+	public void setIntHeader(String name, int value) {
+		headerMap.put(name, new String[] {String.valueOf(value)});
+	}
+
+	@Override /* HttpServletResponse */
+	public void addIntHeader(String name, int value) {
+		headerMap.put(name, new String[] {String.valueOf(value)});
+	}
+
+	@Override /* HttpServletResponse */
+	public void setStatus(int sc) {
+		this.sc = sc;
+	}
+
+	@Override /* HttpServletResponse */
+	public void setStatus(int sc, String sm) {
+		this.sc = sc;
+		this.msg = sm;
+	}
+
+	@Override /* HttpServletResponse */
+	public int getStatus() {
+		return sc;
+	}
+
+	@Override /* HttpServletResponse */
+	public String getHeader(String name) {
+		String[] s = headerMap.get(name);
+		return s == null || s.length == 0 ? null : s[0];
+	}
+
+	@Override /* HttpServletResponse */
+	public Collection<String> getHeaders(String name) {
+		String[] s = headerMap.get(name);
+		return s == null ? Collections.EMPTY_LIST : Arrays.asList(s);
+	}
+
+	@Override /* HttpServletResponse */
+	public Collection<String> getHeaderNames() {
+		return headerMap.keySet();
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestUtils.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
similarity index 94%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestUtils.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
index e3ee38c..14653c2 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestUtils.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
@@ -10,7 +10,7 @@
 // * "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;
+package org.apache.juneau.rest.util;
 
 import static org.apache.juneau.internal.ArrayUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
@@ -163,7 +163,13 @@ public final class RestUtils {
 		}
 	}
 
-	static String[] parseHeader(String s) {
+	/**
+	 * Parses HTTP header.
+	 * 
+	 * @param s The string to parse.
+	 * @return The parsed string.
+	 */
+	public static String[] parseHeader(String s) {
 		int i = s.indexOf(':');
 		if (i == -1)
 			return null;
@@ -174,8 +180,11 @@ public final class RestUtils {
 
 	/**
 	 * Parses key/value pairs separated by either : or =
+	 * 
+	 * @param s The string to parse.
+	 * @return The parsed string.
 	 */
-	static String[] parseKeyValuePair(String s) {
+	public static String[] parseKeyValuePair(String s) {
 		int i = -1;
 		for (int j = 0; j < s.length() && i < 0; j++) {
 			char c = s.charAt(j);

-- 
To stop receiving notification emails like this one, please contact
jamesbognar@apache.org.