juneau-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jamesbog...@apache.org
Subject [juneau] branch master updated: Tests
Date Sat, 12 May 2018 16:08:59 GMT
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 a447cb4  Tests
a447cb4 is described below

commit a447cb4a8d62b8b7a23b19714887b2f4adc36e2d
Author: JamesBognar <jamesbognar@apache.org>
AuthorDate: Sat May 12 12:08:45 2018 -0400

    Tests
---
 .../org/apache/juneau/testutils/TestUtils.java     |   6 +
 .../src/main/java/org/apache/juneau/ClassMeta.java |  74 +++
 juneau-doc/src/main/javadoc/overview.html          | 176 +++++-
 .../rest/test/DefaultContentTypesResource.java     | 140 -----
 .../apache/juneau/rest/test/ParamsResource.java    |   2 +-
 .../java/org/apache/juneau/rest/test/Root.java     |   1 -
 .../juneau/rest/test/DefaultContentTypesTest.java  | 427 -------------
 .../org/apache/juneau/rest/test/ParamsTest.java    |   2 +-
 .../org/apache/juneau/rest/test/_TestSuite.java    |   1 -
 .../apache/juneau/rest/BasicRestCallHandler.java   |   3 +
 .../java/org/apache/juneau/rest/RequestBody.java   |  29 +-
 .../java/org/apache/juneau/rest/RestContext.java   |  12 +-
 .../org/apache/juneau/rest/RestParamDefaults.java  |   9 -
 .../org/apache/juneau/rest/annotation/Body.java    |  77 ++-
 .../juneau/rest/mock/MockServletRequest.java       |  78 ++-
 .../juneau/rest/mock/MockServletResponse.java      |  44 ++
 .../juneau/rest/annotation/BodyAnnotationTest.java | 680 +++++++++++++++++++++
 .../apache/juneau/rest/annotation/BodyTest.java    | 533 ----------------
 .../rest/annotation/DefaultContentTypesTest.java   | 384 ++++++++++++
 .../juneau/rest/annotation/StatusCodesTest.java    |  48 ++
 20 files changed, 1565 insertions(+), 1161 deletions(-)

diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/testutils/TestUtils.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/testutils/TestUtils.java
index cb662e7..abdfced 100644
--- a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/testutils/TestUtils.java
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/testutils/TestUtils.java
@@ -474,6 +474,12 @@ public class TestUtils {
 		if (! isEquals(expected, actual))
 			throw new ComparisonFailure(format(msg, args), toString(expected), toString(actual));
 	}
+	
+	public static final void assertContains(Object value, String...substrings) {
+		for (String substring : substrings) 
+			if (! contains(toString(value), substring))
+				throw new ComparisonFailure("Text did not contain expected substring.", toString(substring), toString(value));
+	}
 
 	/**
 	 * Creates a ClassMeta for the given types.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
index a3e6ddc..1913e26 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
@@ -119,6 +119,9 @@ public final class ClassMeta<T> implements Type {
 	private final BeanRegistry beanRegistry;                // The bean registry of this class meta (if it has one).
 	private final ClassMeta<?>[] args;                      // Arg types if this is an array of args.
 	private final Object example;                          // Example object.
+	private final Transform<Reader,T> readerTransform;
+	private final Transform<InputStream,T> inputStreamTransform;
+	private final Transform<String,T> stringTransform;
 	
 	private ReadWriteLock lock = new ReentrantReadWriteLock(false);
 	private Lock rLock = lock.readLock(), wLock = lock.writeLock();
@@ -198,6 +201,9 @@ public final class ClassMeta<T> implements Type {
 			this.exampleField = builder.exampleField;
 			this.example = builder.example;
 			this.args = null;
+			this.readerTransform = builder.readerTransform;
+			this.inputStreamTransform = builder.inputStreamTransform;
+			this.stringTransform = builder.stringTransform;
 		} finally {
 			wLock.unlock();
 		}
@@ -260,6 +266,9 @@ public final class ClassMeta<T> implements Type {
 		this.exampleField = mainType.exampleField;
 		this.example = mainType.example;
 		this.args = null;
+		this.readerTransform = mainType.readerTransform;
+		this.inputStreamTransform = mainType.inputStreamTransform;
+		this.stringTransform = mainType.stringTransform;
 	}
 
 	/**
@@ -309,6 +318,9 @@ public final class ClassMeta<T> implements Type {
 		this.exampleMethod = null;
 		this.exampleField = null;
 		this.example = null;
+		this.readerTransform = null;
+		this.inputStreamTransform = null;
+		this.stringTransform = null;
 	}
 
 	@SuppressWarnings({"unchecked","rawtypes","hiding"})
@@ -361,6 +373,9 @@ public final class ClassMeta<T> implements Type {
 		Method exampleMethod;
 		Field exampleField;
 		Object example;
+		Transform<Reader,T> readerTransform;
+		Transform<InputStream,T> inputStreamTransform;
+		Transform<String,T> stringTransform;
 
 		ClassMetaBuilder(Class<T> innerClass, BeanContext beanContext, Class<? extends T> implClass, BeanFilter beanFilter, PojoSwap<T,?>[] pojoSwaps, PojoSwap<?,?>[] childPojoSwaps, Object example) {
 			this.innerClass = innerClass;
@@ -451,6 +466,7 @@ public final class ClassMeta<T> implements Type {
 					}
 				}
 			}
+			// TODO - should use transforms for above code.
 
 			// Special cases
 			try {
@@ -748,6 +764,10 @@ public final class ClassMeta<T> implements Type {
 			}
 			
 			this.example = example;
+			
+			this.readerTransform = TransformCache.get(Reader.class, c);
+			this.inputStreamTransform = TransformCache.get(InputStream.class, c);
+			this.stringTransform = TransformCache.get(String.class, c);
 		}
 
 		private BeanFilter findBeanFilter() {
@@ -2026,4 +2046,58 @@ public final class ClassMeta<T> implements Type {
 	public int hashCode() {
 		return super.hashCode();
 	}
+
+	/**
+	 * Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from a Reader.
+	 * 
+	 * @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from a Reader.
+	 */
+	public boolean hasReaderTransform() {
+		return readerTransform != null;
+	}
+	
+	/**
+	 * Returns the transform for this class for creating instances from a Reader.
+	 * 
+	 * @return The transform, or <jk>null</jk> if no such transform exists.
+	 */
+	public Transform<Reader,T> getReaderTransform() {
+		return readerTransform;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from an InputStream.
+	 * 
+	 * @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from an InputStream.
+	 */
+	public boolean hasInputStreamTransform() {
+		return inputStreamTransform != null;
+	}
+
+	/**
+	 * Returns the transform for this class for creating instances from an InputStream.
+	 * 
+	 * @return The transform, or <jk>null</jk> if no such transform exists.
+	 */
+	public Transform<InputStream,T> getInputStreamTransform() {
+		return inputStreamTransform;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if this class has a transform associated with it that allows it to be created from a String.
+	 * 
+	 * @return <jk>true</jk> if this class has a transform associated with it that allows it to be created from a String.
+	 */
+	public boolean hasStringTransform() {
+		return stringTransform != null;
+	}
+
+	/**
+	 * Returns the transform for this class for creating instances from a String.
+	 * 
+	 * @return The transform, or <jk>null</jk> if no such transform exists.
+	 */
+	public Transform<String,T> getStringTransform() {
+		return stringTransform;
+	}
 }
diff --git a/juneau-doc/src/main/javadoc/overview.html b/juneau-doc/src/main/javadoc/overview.html
index 4d88d55..29d01ca 100644
--- a/juneau-doc/src/main/javadoc/overview.html
+++ b/juneau-doc/src/main/javadoc/overview.html
@@ -12279,40 +12279,172 @@
 	<h3 class='topic' onclick='toggle(this)'>7.7 - @Body</h3>
 	<div class='topic'>
 		<p>
-			The {@link org.apache.juneau.rest.annotation.Body @Body} annotation provides easy access to the HTTP body content as any <a class='doclink' href='#juneau-marshall.PojoCategories'>parsable</a> POJO type
-			<br>In the example below, we're POSTing beans.
+			The {@link org.apache.juneau.rest.annotation.Body @Body} annotation is used to identify POJOs to be used as the body of an HTTP request.
 		</p>
+		<h5 class='figure'>Example:</h5>
 		<p class='bcode w800'>
-	<jc>// Example POST of a bean</jc>
-	<ja>@RestMethod</ja>(name=<jsf>POST</jsf>, path=<js>"/"</js>)
-	<jk>public void</jk> doPost(<ja>@Body</ja> Person person) <jk>throws</jk> Exception {
-		<jc>// Do something with person.</jc>
+	<ja>@RestMethod</ja>(name=<jsf>POST</jsf>)
+	<jk>public void</jk> addPerson(<ja>@Body</ja> Person person) { 
+		... 
 	}
 		</p>
 		<p>
-			The HTTP body of a request can be retrieved as a parsed POJO using either the 
-			{@link org.apache.juneau.rest.RestRequest#getBody()} method, or a parameter annotated with 
-			{@link org.apache.juneau.rest.annotation.Body @Body}.
+			This is functionally equivalent to the following code...
 		</p>
 		<p class='bcode w800'>
-	<jc>// Equivalent method 1</jc>
-	<ja>@RestMethod</ja>(name=<jsf>POST</jsf>, path=<js>"/example1"</js>)
-	<jk>public void</jk> doPost1(<ja>@Body</ja> Person p) {
-		<jc>// Do something with p.</jc>
+	<ja>@RestMethod</ja>(name=<jsf>POST</jsf>)
+	<jk>public void</jk> addPerson(RestRequest req) {
+		Person person = req.getBody().asType(Person.<jk>class</jk>);
+		...
 	}
-
-	<jc>// Equivalent method 2</jc>
-	<ja>@RestMethod</ja>(name=<jsf>POST</jsf>, path=<js>"/example2"</js>)
-	<jk>public void</jk> doPost2(RestRequest req) {
-		Person p = req.getBody).asType(Person.<jk>class</jk>);
-		<jc>// Do something with p.</jc>
+		</p>
+		<p>
+			This annotation can be applied at the following locations:
+		</p>
+		<ul class='spaced-list'>
+			<li>
+				Parameters on a  {@link RestMethod @RestMethod}.
+			<li>
+				POJO classes.
+		</ul>
+		
+		<p>
+			Any of the following types can be used for the parameter or POJO class:
+		</p>
+		<ol class='spaced-list'>
+			<li>
+				{@link Reader}
+				<br><ja>@Body</ja> annotation is optional.
+				<br><code>Content-Type</code> is ignored.
+			<li>
+				{@link InputStream} 
+				<br><ja>@Body</ja> annotation is optional.
+				<br><code>Content-Type</code> is ignored.
+			<li>
+				Any <a class='doclink' href='../../../../../overview-summary.html#juneau-marshall.PojoCategories'>parsable</a> POJO type.
+				<br><code>Content-Type</code> is required to identify correct parser.
+			<li>
+				Objects convertible from {@link Reader} by having one of the following non-deprecated methods:
+				<ul>
+					<li><code><jk>public</jk> T(Reader in) {...}</code>
+					<li><code><jk>public static</jk> T <jsm>create</jsm>(Reader in) {...}</code>
+					<li><code><jk>public static</jk> T <jsm>fromReader</jsm>(Reader in) {...}</code>
+				</ul>
+				<br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO.
+			<li>
+				Objects convertible from {@link InputStream} by having one of the following non-deprecated methods:
+				<ul>
+					<li><code><jk>public</jk> T(InputStream in) {...}</code>
+					<li><code><jk>public static</jk> T <jsm>create</jsm>(InputStream in) {...}</code>
+					<li><code><jk>public static</jk> T <jsm>fromInputStream</jsm>(InputStream in) {...}</code>
+				</ul>
+				<br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO.
+			<li>
+				Objects convertible from {@link String} (including <code>String</code> itself) by having one of the following non-deprecated methods:
+				<ul>
+					<li><code><jk>public</jk> T(String in) {...}</code> (e.g. {@link Integer}, {@link Boolean})
+					<li><code><jk>public static</jk> T <jsm>create</jsm>(String in) {...}</code> 
+					<li><code><jk>public static</jk> T <jsm>fromString</jsm>(String in) {...}</code>
+					<li><code><jk>public static</jk> T <jsm>fromValue</jsm>(String in) {...}</code>
+					<li><code><jk>public static</jk> T <jsm>valueOf</jsm>(String in) {...}</code> (e.g. enums)
+					<li><code><jk>public static</jk> T <jsm>parse</jsm>(String in) {...}</code> (e.g. {@link Level})
+					<li><code><jk>public static</jk> T <jsm>parseString</jsm>(String in) {...}</code>
+					<li><code><jk>public static</jk> T <jsm>forName</jsm>(String in) {...}</code> (e.g. {@link Class}, {@link Charset})
+					<li><code><jk>public static</jk> T <jsm>forString</jsm>(String in) {...}</code>
+				</ul>
+				<br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO.
+		</ol>
+		<p>
+			Special note should be taken on using {@link String} as the body object:
+		</p>
+		<p class='bcode w800'>
+	<ja>@RestMethod</ja>(name=<jsf>POST</jsf>)
+	<jk>public void</jk> add(<ja>@Body</ja> String body) { 
+		... 
 	}
 		</p>
 		<p>
-			The Juneau framework will automatically determine the appropriate <l>Parser</l> to use based on the 
-			<l>Content-Type</l> HTTP header.  
-			<br>So the body content could be JSON or XML or any other supported parsing types.
+			In the case above, when the <code>Content-Type</code> header is present and matches a parser, the body will be parsed into a <code>String</code> using the matched parser
+			using rule #3 above.
+			<br>For example, passing in a body of <js>"'foo'"</js> with the header <code>Content-Type: application/json</code> will result in the body being populated with
+			<js>"foo"</js>.
+		</p>
+		<p>
+			When the <code>Content-Type</code> header is not present or match a parser, the body is pass as-is using rule #6 above.
+			<br>For example, passing in a body of <js>"'foo'"</js> with no <code>Content-Type</code> header will result in the body being populated with
+			<js>"'foo'"</js>.
 		</p>
+		<p>
+			The <ja>@Body</ja> annotation can also be used on POJO classes used as parameters on a REST method.
+			<br>This can be useful if you're reusing the same POJOs often in your REST interface and don't want to redefine your Swagger on each method.
+		</p>
+		
+		<h5 class='figure'>Examples:</h5>
+		<p class='bcode w800'>
+	<ja>@Body</ja>(description=<js>"POJO convertible from String"</js>)</ja>
+	<jk>public static class</jk> A {
+		<jk>private</jk> String <jf>val</jf>;
+		<jk>public</jk> A(String val) {
+			<jk>this</jk>.<jf>val</jf> = val;
+		}
+		<jk>public</jk> String toString() {
+			<jk>return</jk> <jf>val</jf>;
+		}
+	}
+
+	<ja>@RestMethod</ja>(name=<jsf>PUT</jsf>, path=<js>"/testPojoFromString"</js>)
+	<jk>public</jk> A testPojoFromString(A a) {
+		<jk>return</jk> a;
+	}
+		</p>
+		<p class='bcode w800'>
+	<ja>@Body</ja>(description=<js>"Bean POJO"</js>)</ja>
+	<jk>public static class</jk> B {
+		<jk>public</jk> String <jf>f1</jf>;
+	}
+
+	<ja>@RestMethod</ja>(name=<jsf>PUT</jsf>, path=<js>"/testBean"</js>)
+	<jk>public</jk> B testBean(B b) {
+		<jk>return</jk> b;
+	}
+		</p>
+		<p class='bcode w800'>
+	<ja>@Body</ja>(description=<js>"Bean list"</js>)</ja>
+	<jk>public static class</jk> C extends LinkedList&lt;B&gt; {}
+	
+	<ja>@RestMethod</ja>(name=<jsf>PUT</jsf>, path=<js>"/testBeanList"</js>)
+	<jk>public</jk> C testString(C c) {
+		<jk>return</jk> c;
+	}
+		</p>
+		<p class='bcode w800'>
+	<ja>@Body</ja>(description=<js>"POJO convertible from input stream"</js>)</ja>
+	<jk>public static class</jk> D {
+		<jk>private</jk> String <jf>val</jf>;
+		<jk>public</jk> D(InputStream is) {
+			<jk>this</jk>.<jf>val</jf> = IOUtils.<jsm>read</jsm>(is);
+		}
+		<jk>public</jk> String toString() {
+			<jk>return</jk> <jf>val</jf>;
+		}
+	}
+
+	<ja>@RestMethod</ja>(name=<jsf>PUT</jsf>, path=<js>"/testPojoFromInputStream"</js>)
+	<jk>public</jk> D testPojoFromInputStream(D d) {
+		<jk>return</jk> d;
+	}
+		</p>
+		
+		<h5 class='section'>Notes:</h5>
+		<ul class='spaced-list'>
+			<li>
+				Annotation parameter values will be aggregated when used on POJO parent and child classes. 
+				<br>Values on child classes override values on parent classes.
+			<li>
+				Annotation parameter values will be aggregated when used on both POJOs and REST methods. 
+				<br>Values on methods override values on POJO classes.
+		</ul>
+		
 		<h5 class='section'>See Also:</h5>
 		<ul>
 			<li class='ja'>{@link org.apache.juneau.rest.annotation.Body}
diff --git a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/DefaultContentTypesResource.java b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/DefaultContentTypesResource.java
deleted file mode 100644
index c15a0d7..0000000
--- a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/DefaultContentTypesResource.java
+++ /dev/null
@@ -1,140 +0,0 @@
-// ***************************************************************************************************************************
-// * 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.test;
-
-import static org.apache.juneau.http.HttpMethodName.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.parser.*;
-import org.apache.juneau.rest.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.serializer.*;
-
-/**
- * JUnit automated testcase resource.
- */
-@RestResource(
-	path="/testDefaultContentTypes",
-	defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "},
-	parsers={DefaultContentTypesResource.P1.class,DefaultContentTypesResource.P2.class}, serializers={DefaultContentTypesResource.S1.class,DefaultContentTypesResource.S2.class}
-)
-public class DefaultContentTypesResource extends RestServlet {
-	private static final long serialVersionUID = 1L;
-
-	public static class P1 extends DummyParser { public P1(PropertyStore ps) {super(ps, "p1", "text/p1");}}
-
-	public static class P2 extends DummyParser { public P2(PropertyStore ps) {super(ps, "p2", "text/p2");}}
-
-	public static class P3 extends DummyParser { public P3(PropertyStore ps) {super(ps, "p3", "text/p3");}}
-
-	public static class S1 extends DummySerializer { public S1(PropertyStore ps) {super(ps, "s1", "text/s1");}}
-
-	public static class S2 extends DummySerializer { public S2(PropertyStore ps) {super(ps, "s2", "text/s2");}}
-
-	public static class S3 extends DummySerializer { public S3(PropertyStore ps) {super(ps, "s3", "text/s3");}}
-
-	/**
-	 * Test that default Accept and Content-Type headers on servlet annotation are picked up.
-	 */
-	@RestMethod(name=PUT, path="/testDefaultHeadersOnServletAnnotation")
-	public String testDefaultHeadersOnServletAnnotation(@Body String in) {
-		return in;
-	}
-
-	//====================================================================================================
-	// Test that default Accept and Content-Type headers on servlet annotation are picked up
-	// when @RestMethod.parsers/serializers annotations are used.
-	//====================================================================================================
-	@RestMethod(name=PUT, path="/testRestMethodParsersSerializers", parsers=P3.class, serializers=S3.class)
-	public String testRestMethodParsersSerializers(@Body String in) {
-		return in;
-	}
-
-	//====================================================================================================
-	// Test that default Accept and Content-Type headers on servlet annotation are picked up
-	// when @RestMethod.addParsers/addSerializers annotations are used.
-	//====================================================================================================
-	@RestMethod(name=PUT, path="/testRestMethodAddParsersSerializers", parsers=P3.class, serializers=S3.class, inherit="SERIALIZERS,PARSERS")
-	public String testRestMethodAddParsersSerializers(@Body String in) {
-		return in;
-	}
-
-	//====================================================================================================
-	// Various Accept incantations.
-	//====================================================================================================
-	@RestMethod(name=PUT, path="/testAccept")
-	public String testAccept(@Body String in) {
-		return in;
-	}
-
-	//====================================================================================================
-	// Test that default Accept and Content-Type headers on method annotation are picked up
-	// when @RestMethod.parsers/serializers annotations are used.
-	//====================================================================================================
-	@RestMethod(name=PUT, path="/testRestMethodParserSerializerAnnotations", defaultRequestHeaders={"Accept: text/s3","Content-Type: text/p3"}, parsers=P3.class, serializers=S3.class)
-	public String testRestMethodParserSerializerAnnotations(@Body String in) {
-		return in;
-	}
-
-	//====================================================================================================
-	// Test that default Accept and Content-Type headers on method annotation are picked up
-	// 	when @RestMethod.addParsers/addSerializers annotations are used.
-	//====================================================================================================
-	@RestMethod(name=PUT, path="/testRestMethodAddParsersSerializersAnnotations", defaultRequestHeaders={"Accept: text/s3","Content-Type: text/p3"}, parsers=P3.class, serializers=S3.class, inherit="SERIALIZERS,PARSERS")
-	public String testRestMethodAddParsersSerializersAnnotations(@Body String in) {
-		return in;
-	}
-
-	public static class DummyParser extends ReaderParser {
-
-		private String name;
-
-		private DummyParser(PropertyStore ps, String name, String...consumes) {
-			super(ps, consumes);
-			this.name = name;
-		}
-
-		@Override /* Parser */
-		public ReaderParserSession createSession(ParserSessionArgs args) {
-			return new ReaderParserSession(args) {
-
-				@Override /* ParserSession */
-				@SuppressWarnings("unchecked")
-				protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception {
-					return (T)name;
-				}
-			};
-		}
-	}
-
-	public static class DummySerializer extends WriterSerializer {
-
-		private String name;
-
-		private DummySerializer(PropertyStore ps, String name, String produces) {
-			super(ps, produces, null);
-			this.name = name;
-		}
-
-		@Override /* Serializer */
-		public WriterSerializerSession createSession(SerializerSessionArgs args) {
-			return new WriterSerializerSession(args) {
-
-				@Override /* SerializerSession */
-				protected void doSerialize(SerializerPipe out, Object o) throws Exception {
-					out.getWriter().write(name + "/" + o);
-				}
-			};
-		}
-	}
-}
diff --git a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
index 69fe0e0..e15ca28 100644
--- a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
+++ b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
@@ -401,7 +401,7 @@ public class ParamsResource extends BasicRestServlet {
 		return t != null;
 	}
 
-	@RestMethod(name=GET, path="/otherObjects/Parser")
+	@RestMethod(name=GET, path="/otherObjects/Parser",parsers={JsonParser.class})
 	public String testOtherParser(Parser t) {
 		return t.getClass().getName();
 	}
diff --git a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/Root.java b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/Root.java
index 3b4973e..119996e 100644
--- a/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/Root.java
+++ b/juneau-microservice/juneau-microservice-test/src/main/java/org/apache/juneau/rest/test/Root.java
@@ -32,7 +32,6 @@ import org.apache.juneau.rest.helper.*;
 		ClientFuturesResource.class,
 		ClientVersionResource.class,
 		ConfigResource.class,
-		DefaultContentTypesResource.class,
 		ErrorConditionsResource.class,
 		TransformsResource.class,
 		FormDataResource.class,
diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/DefaultContentTypesTest.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/DefaultContentTypesTest.java
deleted file mode 100644
index 9ce9e19..0000000
--- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/DefaultContentTypesTest.java
+++ /dev/null
@@ -1,427 +0,0 @@
-// ***************************************************************************************************************************
-// * 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.test;
-
-import static javax.servlet.http.HttpServletResponse.*;
-import static org.apache.juneau.microservice.testutils.TestUtils.*;
-import static org.junit.Assert.*;
-
-import org.apache.juneau.rest.client.*;
-import org.junit.*;
-
-
-public class DefaultContentTypesTest extends RestTestcase {
-
-	private static String URL = "/testDefaultContentTypes";
-	private static boolean debug = false;
-
-	//====================================================================================================
-	// Test that default Accept and Content-Type headers on servlet annotation are picked up.
-	//====================================================================================================
-	@Test
-	public void testDefaultHeadersOnServletAnnotation() throws Exception {
-		RestClient client = TestMicroservice.DEFAULT_CLIENT;
-		String r;
-
-		String url = URL + "/testDefaultHeadersOnServletAnnotation";
-
-		r = client.doPut(url, "").accept("").contentType("").getResponseAsString();
-		assertEquals("s2/p2", r);
-
-		r = client.doPut(url, "").accept("text/s1").contentType("").getResponseAsString();
-		assertEquals("s1/p2", r);
-
-		r = client.doPut(url, "").accept("").contentType("text/p1").getResponseAsString();
-		assertEquals("s2/p1", r);
-
-		r = client.doPut(url, "").accept("text/s1").contentType("text/p1").getResponseAsString();
-		assertEquals("s1/p1", r);
-
-		r = client.doPut(url, "").accept("text/s2").contentType("").getResponseAsString();
-		assertEquals("s2/p2", r);
-
-		r = client.doPut(url, "").accept("").contentType("text/p2").getResponseAsString();
-		assertEquals("s2/p2", r);
-
-		r = client.doPut(url, "").accept("text/s2").contentType("text/p2").getResponseAsString();
-		assertEquals("s2/p2", r);
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s3").contentType("").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE,
-				"Unsupported media-type in request header 'Accept': 'text/s3'",
-				"Supported media-types: ['text/s1','text/s2']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p3").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p3'",
-				"Supported media-types: ['text/p1','text/p2']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s3").contentType("text/p3").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p3'",
-				"Supported media-types: ['text/p1','text/p2']"
-			);
-		}
-	}
-
-	//====================================================================================================
-	// Test that default Accept and Content-Type headers on servlet annotation are picked up
-	// when @RestMethod.parsers/serializers annotations are used.
-	//====================================================================================================
-	@Test
-	public void testRestMethodParsersSerializers() throws Exception {
-		RestClient client = TestMicroservice.DEFAULT_CLIENT;
-		String r;
-
-		String url = URL + "/testRestMethodParsersSerializers";
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("").contentType("").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s1").contentType("").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p1").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p1'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s1").contentType("text/p1").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p1'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s2").contentType("").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p2").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s2").contentType("text/p2").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s3").contentType("").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p3").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE,
-				"Unsupported media-type in request header 'Accept': 'text/s2'",
-				"Supported media-types: ['text/s3']"
-			);
-		}
-
-		r = client.doPut(url, "").accept("text/s3").contentType("text/p3").getResponseAsString();
-		assertEquals("s3/p3", r);
-	}
-
-	//====================================================================================================
-	// Test that default Accept and Content-Type headers on servlet annotation are picked up
-	// when @RestMethod.addParsers/addSerializers annotations are used.
-	//====================================================================================================
-	@Test
-	public void testRestMethodAddParsersSerializers() throws Exception {
-		RestClient client = TestMicroservice.DEFAULT_CLIENT;
-		String r;
-
-		String url = URL + "/testRestMethodAddParsersSerializers";
-
-		r = client.doPut(url, "").accept("").contentType("").getResponseAsString();
-		assertEquals("s2/p2", r);
-
-		r = client.doPut(url, "").accept("text/s1").contentType("").getResponseAsString();
-		assertEquals("s1/p2", r);
-
-		r = client.doPut(url, "").accept("").contentType("text/p1").getResponseAsString();
-		assertEquals("s2/p1", r);
-
-		r = client.doPut(url, "").accept("text/s1").contentType("text/p1").getResponseAsString();
-		assertEquals("s1/p1", r);
-
-		r = client.doPut(url, "").accept("text/s2").contentType("").getResponseAsString();
-		assertEquals("s2/p2", r);
-
-		r = client.doPut(url, "").accept("").contentType("text/p2").getResponseAsString();
-		assertEquals("s2/p2", r);
-
-		r = client.doPut(url, "").accept("text/s2").contentType("text/p2").getResponseAsString();
-		assertEquals("s2/p2", r);
-
-		r = client.doPut(url, "").accept("text/s3").contentType("").getResponseAsString();
-		assertEquals("s3/p2", r);
-
-		r = client.doPut(url, "").accept("").contentType("text/p3").getResponseAsString();
-		assertEquals("s2/p3", r);
-
-		r = client.doPut(url, "").accept("text/s3").contentType("text/p3").getResponseAsString();
-		assertEquals("s3/p3", r);
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p4").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			// Note that parsers defined on method are listed before parsers defined on class.
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p4'",
-				"Supported media-types: ['text/p3','text/p1','text/p2']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s4").contentType("").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			// Note that serializers defined on method are listed before serializers defined on class.
-			checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE,
-				"Unsupported media-type in request header 'Accept': 'text/s4'",
-				"Supported media-types: ['text/s3','text/s1','text/s2']"
-			);
-		}
-	}
-
-	//====================================================================================================
-	// Various Accept incantations.
-	//====================================================================================================
-	@Test
-	public void testAccept() throws Exception {
-		RestClient client = TestMicroservice.client().contentType("text/p1").build();
-		String r;
-
-		String url = URL + "/testAccept";
-
-		// "*/*" should match the first serializer, not the default serializer.
-		r = client.doPut(url, "").accept("*/*").getResponseAsString();
-		assertEquals("s1/p1", r);
-
-		// "text/*" should match the first serializer, not the default serializer.
-		r = client.doPut(url, "").accept("text/*").getResponseAsString();
-		assertEquals("s1/p1", r);
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("bad/*").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE,
-				"Unsupported media-type in request header 'Accept': 'bad/*'",
-				"Supported media-types: ['text/s1','text/s2']"
-			);
-		}
-
-		r = client.doPut(url, "").accept("bad/*,text/*").getResponseAsString();
-		assertEquals("s1/p1", r);
-
-		r = client.doPut(url, "").accept("text/*,bad/*").getResponseAsString();
-		assertEquals("s1/p1", r);
-
-		r = client.doPut(url, "").accept("text/s1;q=0.5,text/s2").getResponseAsString();
-		assertEquals("s2/p1", r);
-
-		r = client.doPut(url, "").accept("text/s1,text/s2;q=0.5").getResponseAsString();
-		assertEquals("s1/p1", r);
-
-		client.closeQuietly();
-	}
-
-	//====================================================================================================
-	// Test that default Accept and Content-Type headers on method annotation are picked up
-	// when @RestMethod.parsers/serializers annotations are used.
-	//====================================================================================================
-	@Test
-	public void testRestMethodParserSerializerAnnotations() throws Exception {
-		RestClient client = TestMicroservice.DEFAULT_CLIENT;
-		String r;
-
-		String url = URL + "/testRestMethodParserSerializerAnnotations";
-
-		r = client.doPut(url, "").accept("").contentType("").getResponseAsString();
-		assertEquals("s3/p3", r);
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s1").contentType("").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE,
-				"Unsupported media-type in request header 'Accept': 'text/s1'",
-				"Supported media-types: ['text/s3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p1").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p1'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s1").contentType("text/p1").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p1'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s2").contentType("").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_NOT_ACCEPTABLE,
-				"Unsupported media-type in request header 'Accept': 'text/s2'",
-				"Supported media-types: ['text/s3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("").contentType("text/p2").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		try {
-			r = client.doPut(url+"?noTrace=true", "").accept("text/s2").contentType("text/p2").getResponseAsString();
-			fail("Exception expected");
-		} catch (RestCallException e) {
-			checkErrorResponse(debug, e, SC_UNSUPPORTED_MEDIA_TYPE,
-				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
-				"Supported media-types: ['text/p3']"
-			);
-		}
-
-		r = client.doPut(url, "").accept("text/s3").contentType("").getResponseAsString();
-		assertEquals("s3/p3", r);
-
-		r = client.doPut(url, "").accept("").contentType("text/p3").getResponseAsString();
-		assertEquals("s3/p3", r);
-
-		r = client.doPut(url, "").accept("text/s3").contentType("text/p3").getResponseAsString();
-		assertEquals("s3/p3", r);
-	}
-
-	//====================================================================================================
-	// Test that default Accept and Content-Type headers on method annotation are picked up
-	// 	when @RestMethod.addParsers/addSerializers annotations are used.
-	//====================================================================================================
-	@Test
-	public void testRestMethodAddParsersSerializersAnnotations() throws Exception {
-		RestClient client = TestMicroservice.DEFAULT_CLIENT;
-		String r;
-
-		String url = URL + "/testRestMethodAddParsersSerializersAnnotations";
-
-		r = client.doPut(url, "").accept("").contentType("").getResponseAsString();
-		assertEquals("s3/p3", r);
-
-		r = client.doPut(url, "").accept("text/s1").contentType("").getResponseAsString();
-		assertEquals("s1/p3", r);
-
-		r = client.doPut(url, "").accept("").contentType("text/p1").getResponseAsString();
-		assertEquals("s3/p1", r);
-
-		r = client.doPut(url, "").accept("text/s1").contentType("text/p1").getResponseAsString();
-		assertEquals("s1/p1", r);
-
-		r = client.doPut(url, "").accept("text/s2").contentType("").getResponseAsString();
-		assertEquals("s2/p3", r);
-
-		r = client.doPut(url, "").accept("").contentType("text/p2").getResponseAsString();
-		assertEquals("s3/p2", r);
-
-		r = client.doPut(url, "").accept("text/s2").contentType("text/p2").getResponseAsString();
-		assertEquals("s2/p2", r);
-
-		r = client.doPut(url, "").accept("text/s3").contentType("").getResponseAsString();
-		assertEquals("s3/p3", r);
-
-		r = client.doPut(url, "").accept("").contentType("text/p3").getResponseAsString();
-		assertEquals("s3/p3", r);
-
-		r = client.doPut(url, "").accept("text/s3").contentType("text/p3").getResponseAsString();
-		assertEquals("s3/p3", r);
-	}
-}
diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/ParamsTest.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/ParamsTest.java
index bd7bf6f..72cd276 100644
--- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/ParamsTest.java
+++ b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/ParamsTest.java
@@ -811,7 +811,7 @@ public class ParamsTest extends RestTestcase {
 
 	@Test
 	public void testOtherParser() throws Exception {
-		String r = CLIENT.doGet(URL + "/otherObjects/Parser").getResponseAsString();
+		String r = CLIENT.doGet(URL + "/otherObjects/Parser").contentType("application/json").getResponseAsString();
 		assertEquals("\"org.apache.juneau.json.JsonParser\"", r);
 	}
 
diff --git a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
index 6841615..724cf8d 100644
--- a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
+++ b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
@@ -31,7 +31,6 @@ import org.junit.runners.Suite.*;
 	ClientFuturesTest.class,
 	ClientVersionTest.class,
 	ConfigTest.class,
-	DefaultContentTypesTest.class,
 	ErrorConditionsTest.class,
 	FormDataTest.class,
 	GroupsTest.class,
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 b0d007b..6ff40d7 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
@@ -167,6 +167,9 @@ public class BasicRestCallHandler implements RestCallHandler {
 				// If not invoked above, see if it's an OPTIONs request
 				if (rc != SC_OK)
 					handleNotFound(rc, req, res);
+				
+				if (res.getStatus() == 0)
+					res.setStatus(rc);
 			}
 
 			if (res.hasOutput()) {
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 b5e7c4a..0df1e58 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
@@ -343,16 +343,19 @@ public class RequestBody {
 	public ParserMatch getParserMatch() {
 		if (mediaType != null && parser != null)
 			return new ParserMatch(mediaType, parser);
+		MediaType mt = getMediaType();
+		return mt == null ? null : parsers.getParserMatch(mt);
+	}
+	
+	private MediaType getMediaType() {
+		if (mediaType != null)
+			return mediaType;
 		MediaType mediaType = headers.getContentType();
-		if (isEmpty(mediaType)) {
-			if (body != null)
-				mediaType = MediaType.UON;
-			else
-				mediaType = MediaType.JSON;
-		}
-		return parsers.getParserMatch(mediaType);
+		if (mediaType == null && body != null) 
+			return MediaType.UON;
+		return mediaType;
 	}
-
+	
 	/**
 	 * Returns the parser matching the request <code>Content-Type</code> header.
 	 * 
@@ -423,7 +426,17 @@ public class RequestBody {
 					);
 				}
 			}
+			
+			if (cm.hasReaderTransform())
+				return cm.getReaderTransform().transform(getReader());
 
+			if (cm.hasInputStreamTransform())
+				return cm.getInputStreamTransform().transform(getInputStream());
+			
+			MediaType mt = getMediaType();
+			if ((isEmpty(mt) || mt.toString().equals("text/plain")) && cm.hasStringTransform())
+				return cm.getStringTransform().transform(asString());
+			
 			throw new UnsupportedMediaType(
 				"Unsupported media-type in request header ''Content-Type'': ''{0}''\n\tSupported media-types: {1}",
 				headers.getContentType(), req.getParsers().getSupportedMediaTypes()
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 097138a..abc0529 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
@@ -3029,10 +3029,16 @@ public final class RestContext extends BeanContext {
 											try {
 												// Parse the args and invoke the method.
 												Parser p = req.getBody().getParser();
-												try (Closeable in = p.isReaderParser() ? req.getReader() : req.getInputStream()) {
-													Object output = m.invoke(o, p.parseArgs(in, m.getGenericParameterTypes()));
-													res.setOutput(output);
+												Object[] args = null;
+												if (m.getGenericParameterTypes().length == 0)
+													args = new Object[0];
+												else {
+													try (Closeable in = p.isReaderParser() ? req.getReader() : req.getInputStream()) {
+														args = p.parseArgs(in, m.getGenericParameterTypes());
+													}
 												}
+												Object output = m.invoke(o, args);
+												res.setOutput(output);
 												return SC_OK;
 											} catch (Exception e) {
 												throw new InternalServerError(e);
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
index a0a33c0..e55623a 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
@@ -589,21 +589,12 @@ class RestParamDefaults {
 
 	static final class BodyObject extends RestMethodParam {
 		
-		private final Transform<Reader,?> readerTransform;
-		private final Transform<InputStream,?> inputStreamTransform;
-
 		protected BodyObject(Method method, Body a, Type type, RestMethodParam existing) {
 			super(BODY, method, null, type, getMetaData(a, castOrNull(existing, BodyObject.class)));
-			readerTransform = TransformCache.get(Reader.class, getTypeClass());
-			inputStreamTransform = TransformCache.get(InputStream.class, getTypeClass());
 		}
 
 		@Override /* RestMethodParam */
 		public Object resolve(RestRequest req, RestResponse res) throws Exception {
-			if (readerTransform != null)
-				return readerTransform.transform(req.getReader());  // Also passes Reader through.
-			if (inputStreamTransform != null)
-				return inputStreamTransform.transform(req.getInputStream());  // Also passes InputStream through.
 			return req.getBody().asType(type);
 		}
 		
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Body.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Body.java
index 085fe31..3d6beb3 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Body.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Body.java
@@ -17,15 +17,19 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.io.*;
 import java.lang.annotation.*;
+import java.nio.charset.*;
+import java.util.logging.*;
 
 import org.apache.juneau.rest.*;
 
 /**
- * Annotation that can be applied to a parameter of a {@link RestMethod @RestMethod} annotated method or POJO class to identify it as the HTTP
- * request body converted to a POJO.
+ * REST request body annotation.
+ * 
+ * <p>
+ * Identifies a POJO to be used as the body of an HTTP request.
  * 
  * <h5 class='section'>Example:</h5>
- * <p class='bcode'>
+ * <p class='bcode w800'>
  * 	<ja>@RestMethod</ja>(name=<jsf>POST</jsf>)
  * 	<jk>public void</jk> addPerson(<ja>@Body</ja> Person person) {
  * 		...
@@ -34,36 +38,81 @@ import org.apache.juneau.rest.*;
  * 
  * <p>
  * This is functionally equivalent to the following code...
- * <p class='bcode'>
+ * <p class='bcode w800'>
  * 	<ja>@RestMethod</ja>(name=<jsf>POST</jsf>)
- * 	<jk>public void</jk> addPerson(RestRequest req, RestResponse res) {
+ * 	<jk>public void</jk> addPerson(RestRequest req) {
  * 		Person person = req.getBody().asType(Person.<jk>class</jk>);
  * 		...
  * 	}
  * </p>
  * 
+ * <p>
+ * This annotation can be applied to the following:
+ * <ul class='spaced-list'>
+ * 	<li>
+ * 		Parameters on a  {@link RestMethod @RestMethod}.
+ * 	<li>
+ * 		POJO classes.
+ * </ul>
+ * 
+ * <p>
  * Any of the following types can be used for the parameter:
- * <ul>
- * 	<li>{@link Reader}
- * 	<li>{@link InputStream}
- * 	<li>Primitives (e.g. <code>String</code>, <jk>int</jk>, <jk>boolean</jk>, etc...)
- * 	<li>Beans
- * 	<li>Maps, collections, or arrays of beans or primitives.
- * 	<li>Any object convertible from a {@link Reader} by having one of the following methods:
+ * <ul class='spaced-list'>
+ * 	<li>
+ * 		{@link Reader}
+ * 		<br><ja>@Body</ja> annotation is optional.
+ * 		<br><code>Content-Type</code> is always ignored.
+ * 	<li>
+ * 		{@link InputStream} 
+ * 		<br><ja>@Body</ja> annotation is optional.
+ * 		<br><code>Content-Type</code> is always ignored.
+ * 	<li>
+ * 		Any <a class='doclink' href='../../../../../overview-summary.html#juneau-marshall.PojoCategories'>parsable</a> POJO type.
+ * 		<br><code>Content-Type</code> is required to identify correct parser.
+ * 	<li>
+ * 		Objects convertible from {@link Reader} by having one of the following non-deprecated methods:
  * 		<ul>
  * 			<li><code><jk>public</jk> T(Reader in) {...}</code>
  * 			<li><code><jk>public static</jk> T <jsm>create</jsm>(Reader in) {...}</code>
  * 			<li><code><jk>public static</jk> T <jsm>fromReader</jsm>(Reader in) {...}</code>
  * 		</ul>
- * 	</ul>
- * 	<li>Any object convertible from an {@link InputStream} by having one of the following methods:
+ * 		<br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO.
+ * 	<li>
+ * 		Objects convertible from {@link InputStream} by having one of the following non-deprecated methods:
  * 		<ul>
  * 			<li><code><jk>public</jk> T(InputStream in) {...}</code>
  * 			<li><code><jk>public static</jk> T <jsm>create</jsm>(InputStream in) {...}</code>
  * 			<li><code><jk>public static</jk> T <jsm>fromInputStream</jsm>(InputStream in) {...}</code>
  * 		</ul>
+ * 		<br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO.
+ * 	<li>
+ * 		Objects convertible from {@link String} (including <code>String</code> itself) by having one of the following non-deprecated methods:
+ * 		<ul>
+ * 			<li><code><jk>public</jk> T(String in) {...}</code> (e.g. {@link Integer}, {@link Boolean})
+ * 			<li><code><jk>public static</jk> T <jsm>create</jsm>(String in) {...}</code> 
+ * 			<li><code><jk>public static</jk> T <jsm>fromString</jsm>(String in) {...}</code>
+ * 			<li><code><jk>public static</jk> T <jsm>fromValue</jsm>(String in) {...}</code>
+ * 			<li><code><jk>public static</jk> T <jsm>valueOf</jsm>(String in) {...}</code> (e.g. enums)
+ * 			<li><code><jk>public static</jk> T <jsm>parse</jsm>(String in) {...}</code> (e.g. {@link Level})
+ * 			<li><code><jk>public static</jk> T <jsm>parseString</jsm>(String in) {...}</code>
+ * 			<li><code><jk>public static</jk> T <jsm>forName</jsm>(String in) {...}</code> (e.g. {@link Class}, {@link Charset})
+ * 			<li><code><jk>public static</jk> T <jsm>forString</jsm>(String in) {...}</code>
+ * 		</ul>
+ * 		<br><code>Content-Type</code> must not be present or match an existing parser so that it's not parsed as a POJO.
+ * </ul>
+ * 
+ * 
+ * <h5 class='section'>Notes:</h5>
+ * <ul class='spaced-list'>
+ * 	<li>
+ * 		Annotation parameter values will be aggregated when used on POJO parent and child classes. 
+ * 		<br>Values on child classes override values on parent classes.
+ * 	<li>
+ * 		Annotation parameter values will be aggregated when used on both POJOs and REST methods. 
+ * 		<br>Values on methods override values on POJO classes.
  * </ul>
  * 
+ * 
  * <h5 class='section'>See Also:</h5>
  * <ul>
  * 	<li class='link'><a class="doclink" href="../../../../../overview-summary.html#juneau-rest-server.Body">Overview &gt; juneau-rest-server &gt; @Body</a>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
index 142b3a2..5369758 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
@@ -84,7 +84,6 @@ public class MockServletRequest implements HttpServletRequest {
 	 */
 	public static MockServletRequest create() {
 		MockServletRequest r = new MockServletRequest();
-		r.header("Accept", "text/json+simple").header("Content-Type", "text/json");
 		return r;
 	}
 	
@@ -110,6 +109,78 @@ public class MockServletRequest implements HttpServletRequest {
 	}
 	
 	/**
+	 * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"application/json"</js>.
+	 * 
+	 * @return This object (for method chaining).
+	 */
+	public MockServletRequest json() {
+		return header("Accept", "application/json").header("Content-Type", "application/json");
+	}
+
+	/**
+	 * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/xml"</js>.
+	 * 
+	 * @return This object (for method chaining).
+	 */
+	public MockServletRequest xml() {
+		return header("Accept", "text/xml").header("Content-Type", "text/xml");
+	}
+	
+	/**
+	 * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/html"</js>.
+	 * 
+	 * @return This object (for method chaining).
+	 */
+	public MockServletRequest html() {
+		return header("Accept", "text/html").header("Content-Type", "text/html");
+	}
+	
+	/**
+	 * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/plain"</js>.
+	 * 
+	 * @return This object (for method chaining).
+	 */
+	public MockServletRequest plainText() {
+		return header("Accept", "text/plain").header("Content-Type", "text/plain");
+	}
+	
+	/**
+	 * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"octal/msgpack"</js>.
+	 * 
+	 * @return This object (for method chaining).
+	 */
+	public MockServletRequest msgpack() {
+		return header("Accept", "octal/msgpack").header("Content-Type", "octal/msgpack");
+	}
+	
+	/**
+	 * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/uon"</js>.
+	 * 
+	 * @return This object (for method chaining).
+	 */
+	public MockServletRequest uon() {
+		return header("Accept", "text/uon").header("Content-Type", "text/uon");
+	}
+	
+	/**
+	 * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"application/x-www-form-urlencoded"</js>.
+	 * 
+	 * @return This object (for method chaining).
+	 */
+	public MockServletRequest urlEnc() {
+		return header("Accept", "application/x-www-form-urlencoded").header("Content-Type", "application/x-www-form-urlencoded");
+	}
+	
+	/**
+	 * Convenience method for setting <code>Accept</code> and <code>Content-Type</code> headers to <js>"text/yaml"</js>.
+	 * 
+	 * @return This object (for method chaining).
+	 */
+	public MockServletRequest yaml() {
+		return header("Accept", "text/yaml").header("Content-Type", "text/yaml");
+	}
+
+	/**
 	 * Fluent setter.
 	 * 
 	 * @param uri The URI of the request.
@@ -140,6 +211,11 @@ public class MockServletRequest implements HttpServletRequest {
 	public MockServletResponse execute() throws Exception {
 		MockServletResponse res = MockServletResponse.create();
 		restContext.getCallHandler().service(this, res);
+		
+		// If the status isn't set, something's broken.
+		if (res.getStatus() == 0)
+			throw new RuntimeException("Response status was 0.");
+		
 		return res;
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java
index 0e5efb2..f9845c6 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletResponse.java
@@ -12,7 +12,10 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest.mock;
 
+import static org.apache.juneau.internal.StringUtils.*;
+
 import java.io.*;
+import java.text.*;
 import java.util.*;
 
 import javax.servlet.*;
@@ -266,4 +269,45 @@ public class MockServletResponse implements HttpServletResponse {
 			throw new RuntimeException(e);
 		}
 	}
+	
+	/**
+	 * Throws an {@link AssertionError} if the response status does not match the expected status.
+	 * 
+	 * @param status The expected status.
+	 * @return This object (for method chaining).
+	 * @throws AssertionError Thrown if status does not match.
+	 */
+	public MockServletResponse assertStatus(int status) throws AssertionError {
+		if (getStatus() != status)
+			throw new AssertionError(MessageFormat.format("Response did not have the expected status. expected=[{0}], actual=[{1}]", status, getStatus()));
+		return this;
+	}
+	
+	/**
+	 * Throws an {@link AssertionError} if the response body does not contain all of the expected substrings.
+	 * 
+	 * @param substrings The expected substrings.
+	 * @return This object (for method chaining).
+	 * @throws AssertionError Thrown if the body does not contain one or more of the expected substrings.
+	 */
+	public MockServletResponse assertBodyContains(String...substrings) throws AssertionError {
+		String text = getBodyAsString(); 
+		for (String substring : substrings) 
+			if (! contains(text, substring))
+				throw new AssertionError(MessageFormat.format("Response did not have the expected substring. expected=[{0}], body=[{1}]", substring, text));
+		return this;
+	}
+
+	/**
+	 * Throws an {@link AssertionError} if the response body does not contain the expected text.
+	 * 
+	 * @param text The expected text of the body.
+	 * @return This object (for method chaining).
+	 * @throws AssertionError Thrown if the body does not contain the expected text.
+	 */
+	public MockServletResponse assertBody(String text) throws AssertionError {
+		if (! StringUtils.isEquals(text, getBodyAsString()))
+			throw new AssertionError(MessageFormat.format("Response did not have the expected text. expected=[{0}], actual=[{1}]", text, getBodyAsString()));
+		return this;
+	}
 }
diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyAnnotationTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyAnnotationTest.java
new file mode 100644
index 0000000..8836379
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyAnnotationTest.java
@@ -0,0 +1,680 @@
+// ***************************************************************************************************************************
+// * 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 static org.apache.juneau.http.HttpMethodName.*;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.rest.mock.*;
+import org.apache.juneau.rest.testutils.*;
+import org.apache.juneau.uon.*;
+import org.junit.*;
+import org.junit.runners.*;
+
+/**
+ * Tests the {@link Body} annotation.
+ */
+@SuppressWarnings({"javadoc","serial"})
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class BodyAnnotationTest {
+	
+	//=================================================================================================================
+	// @Body on parameter
+	//=================================================================================================================
+	
+	@RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class)
+	public static class A {
+		
+		@RestMethod(name=PUT, path="/String")
+		public String a01(@Body String b) {
+			return b;
+		}
+
+		@RestMethod(name=PUT, path="/Integer")
+		public Integer a02(@Body Integer b) {
+			return b;
+		}
+
+		@RestMethod(name=PUT, path="/int")
+		public Integer a03(@Body int b) {
+			return b;
+		}
+
+		@RestMethod(name=PUT, path="/Boolean")
+		public Boolean a04(@Body Boolean b) {
+			return b;
+		}
+
+		@RestMethod(name=PUT, path="/boolean")
+		public Boolean a05(@Body boolean b) {
+			return b;
+		}
+
+		@RestMethod(name=PUT, path="/float")
+		public float a06(@Body float f) {
+			return f;
+		}
+
+		@RestMethod(name=PUT, path="/Float")
+		public Float a07(@Body Float f) {
+			return f;
+		}
+		
+		@RestMethod(name=PUT, path="/Map")
+		public TreeMap<String,Integer> a08(@Body TreeMap<String,Integer> m) {
+			return m;
+		}
+
+		@RestMethod(name=PUT, path="/enum")
+		public TestEnum a09(@Body TestEnum e) {
+			return e;
+		}
+
+		public static class A11 {
+			public String f1;
+		}
+		
+		@RestMethod(name=PUT, path="/Bean")
+		public A11 a11(@Body A11 b) {
+			return b;
+		}
+
+		@RestMethod(name=PUT, path="/InputStream")
+		public String a12(@Body InputStream b) throws Exception {
+			return IOUtils.read(b);
+		}
+
+		@RestMethod(name=PUT, path="/Reader")
+		public String a13(@Body Reader b) throws Exception {
+			return IOUtils.read(b);
+		}
+
+		@RestMethod(name=PUT, path="/InputStreamTransform")
+		public A14 a14(@Body A14 b) throws Exception {
+			return b;
+		}
+		public static class A14 {
+			String s;
+			public A14(InputStream in) throws Exception { this.s = IOUtils.read(in); }
+			@Override public String toString() { return s; }
+		}
+		
+		@RestMethod(name=PUT, path="/ReaderTransform")
+		public A15 a15(@Body A15 b) throws Exception {
+			return b;
+		}
+		public static class A15 {
+			private String s;
+			public A15(Reader in) throws Exception { this.s = IOUtils.read(in); }
+			@Override public String toString() { return s; }
+		}
+		
+		@RestMethod(name=PUT, path="/StringTransform")
+		public A16 a16(@Body A16 b) throws Exception { return b; }
+		public static class A16 {
+			private String s;
+			public A16(String s) throws Exception { this.s = s; }
+			@Override public String toString() { return s; }
+		}
+		
+	}
+	
+	private static MockRest a = MockRest.create(A.class);
+	
+	@Test
+	public void a01a_onParameter_String() throws Exception {
+		a.request("PUT", "/String").body("'foo'").json().execute().assertBody("'foo'");
+	}
+	@Test
+	public void a01b_onParameter_String_noContentType() throws Exception {
+		// If no Content-Type specified, should be treated as plain-text.
+		a.request("PUT", "/String").body("'foo'").execute().assertBody("'\\'foo\\''");
+	}
+	@Test
+	public void a01c_onParameter_String_noContentType_other() throws Exception {
+		// If Content-Type not matched, should be treated as plain-text.
+		a.request("PUT", "/String").body("'foo'").contentType("").execute().assertBody("'\\'foo\\''");
+		a.request("PUT", "/String").body("'foo'").contentType("text/plain").execute().assertBody("'\\'foo\\''");
+	}
+	@Test
+	public void a02a_onParameter_Integer() throws Exception {
+		a.request("PUT", "/Integer").body("123").json().execute().assertBody("123");
+	}
+	@Test
+	public void a02b_onParameter_Integer_noContentType() throws Exception {
+		// Integer takes in a String arg, so it can be parsed without Content-Type.
+		a.request("PUT", "/Integer").body("123").execute().assertBody("123");
+	}
+	@Test
+	public void a03a_onParameter_int() throws Exception {
+		a.request("PUT", "/int").body("123").json().execute().assertBody("123");
+	}
+	@Test
+	public void a03b_onParameter_int_noContentType() throws Exception {
+		a.request("PUT", "/int?noTrace=true").body("123").execute().assertBodyContains("Unsupported Media Type");
+	}
+	@Test
+	public void a04a_onParameter_Boolean() throws Exception {
+		a.request("PUT", "/Boolean").body("true").json().execute().assertBody("true");
+	}
+	@Test
+	public void a04b_onParameter_Boolean_noContentType() throws Exception {
+		// Boolean takes in a String arg, so it can be parsed without Content-Type.
+		a.request("PUT", "/Boolean").body("true").execute().assertBody("true");
+	}
+	@Test
+	public void a05a_onParameter_boolean() throws Exception {
+		a.request("PUT", "/boolean").body("true").json().execute().assertBody("true");
+	}
+	@Test
+	public void a05b_onParameter_boolean_noContentType() throws Exception {
+		a.request("PUT", "/boolean?noTrace=true").body("true").execute().assertBodyContains("Unsupported Media Type");
+	}
+	@Test
+	public void a06a_onParameter_float() throws Exception {
+		a.request("PUT", "/float").body("1.23").json().execute().assertBody("1.23");
+	}
+	@Test
+	public void a06b_onParameter_float_noContentType() throws Exception {
+		a.request("PUT", "/float?noTrace=true").body("1.23").execute().assertBodyContains("Unsupported Media Type");
+	}
+	@Test
+	public void a07a_onParameter_Float() throws Exception {
+		a.request("PUT", "/Float").body("1.23").json().execute().assertBody("1.23");
+	}
+	@Test
+	public void a07b_onParameter_Float_noContentType() throws Exception {
+		// Float takes in a String arg, so it can be parsed without Content-Type.
+		a.request("PUT", "/Float").body("1.23").execute().assertBody("1.23");
+	}
+	@Test
+	public void a08a_onParameter_Map() throws Exception {
+		a.request("PUT", "/Map").body("{foo:123}").json().execute().assertBody("{foo:123}");
+	}
+	@Test
+	public void a08b_onParameter_Map_noContentType() throws Exception {
+		a.request("PUT", "/Map?noTrace=true").body("{foo:123}").execute().assertBodyContains("Unsupported Media Type");
+	}
+	@Test
+	public void a09a_onParameter_enum() throws Exception {
+		a.request("PUT", "/enum").body("'ONE'").json().execute().assertBody("'ONE'");
+	}
+	@Test
+	public void a09b_onParameter_enum_noContentType() throws Exception {
+		a.request("PUT", "/enum").body("ONE").execute().assertBody("'ONE'");
+	}
+	@Test
+	public void a11a_onParameter_Bean() throws Exception {
+		a.request("PUT", "/Bean").body("{f1:'a'}").json().execute().assertBody("{f1:'a'}");
+	}
+	@Test
+	public void a11b_onParameter_Bean_noContentType() throws Exception {
+		a.request("PUT", "/Bean?noTrace=true").body("{f1:'a'}").execute().assertBodyContains("Unsupported Media Type");
+	}
+	@Test
+	public void a12a_onParameter_InputStream() throws Exception {
+		// Content-Type should always be ignored.
+		a.request("PUT", "/InputStream").body("'a'").json().execute().assertBody("'\\'a\\''");
+	}
+	@Test
+	public void a12b_onParameter_InputStream_noContentType() throws Exception {
+		a.request("PUT", "/InputStream").body("'a'").execute().assertBody("'\\'a\\''");
+	}
+	@Test
+	public void a13a_onParameter_Reader() throws Exception {
+		// Content-Type should always be ignored.
+		a.request("PUT", "/Reader").body("'a'").json().execute().assertBody("'\\'a\\''");
+	}
+	@Test
+	public void a13b_onParameter_Reader_noContentType() throws Exception {
+		a.request("PUT", "/Reader").body("'a'").execute().assertBody("'\\'a\\''");
+	}
+	@Test
+	public void a14a_onParameter_InputStreamTransform() throws Exception {
+		// Input stream transform requests must not specify Content-Type or else gets resolved as POJO.
+		a.request("PUT", "/InputStreamTransform?noTrace=true").body("'a'").json().execute().assertBodyContains("Bad Request");
+	}
+	@Test
+	public void a14b_onParameter_InputStreamTransform_noContentType() throws Exception {
+		a.request("PUT", "/InputStreamTransform").body("'a'").execute().assertBody("'\\'a\\''");
+	}
+	@Test
+	public void a15a_onParameter_ReaderTransform() throws Exception {
+		// Reader transform requests must not specify Content-Type or else gets resolved as POJO.
+		a.request("PUT", "/ReaderTransform?noTrace=true").body("'a'").json().execute().assertBodyContains("Bad Request");
+	}
+	@Test
+	public void a15b_onParameter_ReaderTransform_noContentType() throws Exception {
+		a.request("PUT", "/ReaderTransform").body("'a'").execute().assertBody("'\\'a\\''");
+	}
+	@Test
+	public void a16a_onParameter_StringTransform() throws Exception {
+		// When Content-Type specified and matched, treated as a parsed POJO.
+		a.request("PUT", "/StringTransform").body("'a'").json().execute().assertBody("'a'");
+	}
+	@Test
+	public void a16b_onParameter_StringTransform_noContentType() throws Exception {
+		// When Content-Type not matched, treated as plain text.
+		a.request("PUT", "/StringTransform").body("'a'").execute().assertBody("'\\'a\\''");
+	}
+	
+	
+	//=================================================================================================================
+	// @Body on POJO
+	//=================================================================================================================
+	
+	@RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class)
+	public static class B {
+		
+		@RestMethod(name=PUT, path="/StringTransform")
+		public B01 b01(B01 b) {
+			return b;
+		}
+		@Body 
+		public static class B01 {
+			private String val;
+			public B01(String val) { this.val = val; }
+			@Override public String toString() { return val; }
+		}
+		
+		@RestMethod(name=PUT, path="/Bean")
+		public B02 b02(B02 b) {
+			return b;
+		}
+		@Body
+		public static class B02 {
+			public String f1;
+		}
+		
+		@RestMethod(name=PUT, path="/BeanList")
+		public B03 b03(B03 b) {
+			return b;
+		}
+		@Body
+		public static class B03 extends LinkedList<B02> {}
+		
+		@RestMethod(name=PUT, path="/InputStreamTransform")
+		public B04 b04(B04 b) throws Exception {
+			return b;
+		}
+		@Body 
+		public static class B04 {
+			String s;
+			public B04(InputStream in) throws Exception { this.s = IOUtils.read(in); }
+			@Override public String toString() { return s; }
+		}
+		
+		@RestMethod(name=PUT, path="/ReaderTransform")
+		public B05 b05(B05 b) throws Exception {
+			return b;
+		}
+		@Body 
+		public static class B05 {
+			private String s;
+			public B05(Reader in) throws Exception { this.s = IOUtils.read(in); }
+			@Override public String toString() { return s; }
+		}
+		
+	}
+	
+	private static MockRest b = MockRest.create(B.class);
+
+	@Test
+	public void b01a_onPojo_StringTransform() throws Exception {
+		b.request("PUT", "/StringTransform").body("'foo'").json().execute().assertBody("'foo'");
+	}
+	@Test
+	public void b01b_onPojo_StringTransform_noContentType() throws Exception {
+		// When Content-Type not matched, treated as plain text.
+		b.request("PUT", "/StringTransform").body("'foo'").execute().assertBody("'\\'foo\\''");
+	}
+	@Test
+	public void b02a_onPojo_Bean() throws Exception {
+		b.request("PUT", "/Bean").body("{f1:'a'}").json().execute().assertBody("{f1:'a'}");
+	}
+	@Test
+	public void b02b_onPojo_Bean_noContentType() throws Exception {
+		b.request("PUT", "/Bean?noTrace=true").body("{f1:'a'}").execute().assertBodyContains("Unsupported Media Type");
+	}
+	@Test
+	public void b03a_onPojo_BeanList() throws Exception {
+		b.request("PUT", "/BeanList").body("[{f1:'a'}]").json().execute().assertBody("[{f1:'a'}]");
+	}
+	@Test
+	public void b03b_onPojo_BeanList_noContentType() throws Exception {
+		b.request("PUT", "/BeanList?noTrace=true").body("[{f1:'a'}]").execute().assertBodyContains("Unsupported Media Type");
+	}
+	@Test
+	public void b04a_onPojo_InputStreamTransform() throws Exception {
+		b.request("PUT", "/InputStreamTransform").body("a").execute().assertBody("'a'");
+	}
+	@Test
+	public void b04b_onPojo_InputStreamTransform_withContentType() throws Exception {
+		// When Content-Type matched, treated as parsed POJO.
+		b.request("PUT", "/InputStreamTransform?noTrace=true").body("a").json().execute().assertBodyContains("Bad Request");
+	}
+	@Test
+	public void b05a_onPojo_ReaderTransform() throws Exception {
+		b.request("PUT", "/ReaderTransform").body("a").execute().assertBody("'a'");
+	}
+	@Test
+	public void b05b_onPojo_ReaderTransform_withContentType() throws Exception {
+		// When Content-Type matched, treated as parsed POJO.
+		b.request("PUT", "/ReaderTransform?noTrace=true").body("a").json().execute().assertBodyContains("Bad Request");
+	}
+
+	
+	//=================================================================================================================
+	// Basic tests using @Body parameter
+	//=================================================================================================================
+
+	public void c01_bodyParam_String() throws Exception {
+		a.request("PUT", "/String?body=foo").execute().assertBody("'foo'");
+		a.request("PUT", "/String?body=null").execute().assertBody("null");
+		a.request("PUT", "/String?body=").execute().assertBody("''");
+	}
+	@Test
+	public void c02_bodyParam_Integer() throws Exception {
+		a.request("PUT", "/Integer?body=123").execute().assertBody("123");
+		a.request("PUT", "/Integer?body=-123").execute().assertBody("-123");
+		a.request("PUT", "/Integer?body=null").execute().assertBody("null");
+		a.request("PUT", "/Integer?body=").execute().assertBody("null");
+		a.request("PUT", "/Integer?body=bad&noTrace=true").execute().assertStatus(400);
+	}
+	@Test
+	public void c03_bodyParam_int() throws Exception {
+		a.request("PUT", "/int?body=123").execute().assertBody("123");
+		a.request("PUT", "/int?body=-123").execute().assertBody("-123");
+		a.request("PUT", "/int?body=null").execute().assertBody("0");
+		a.request("PUT", "/int?body=").execute().assertBody("0");
+		a.request("PUT", "/int?body=bad&noTrace=true").execute().assertStatus(400);
+	}
+	@Test
+	public void c04_bodyParam_Boolean() throws Exception {
+		a.request("PUT", "/Boolean?body=true").execute().assertBody("true");
+		a.request("PUT", "/Boolean?body=false").execute().assertBody("false");
+		a.request("PUT", "/Boolean?body=null").execute().assertBody("null");
+		a.request("PUT", "/Boolean?body=").execute().assertBody("null");
+		a.request("PUT", "/Boolean?body=bad&noTrace=true").execute().assertStatus(400);
+	}
+	@Test
+	public void c05_bodyParam_boolean() throws Exception {
+		a.request("PUT", "/boolean?body=true").execute().assertBody("true");
+		a.request("PUT", "/boolean?body=false").execute().assertBody("false");
+		a.request("PUT", "/boolean?body=null").execute().assertBody("false");
+		a.request("PUT", "/boolean?body=").execute().assertBody("false");
+		a.request("PUT", "/boolean?body=bad&noTrace=true").execute().assertStatus(400);
+	}
+	@Test
+	public void c06_bodyParam_Float() throws Exception {
+		a.request("PUT", "/Float?body=1.23").execute().assertBody("1.23");
+		a.request("PUT", "/Float?body=-1.23").execute().assertBody("-1.23");
+		a.request("PUT", "/Float?body=null").execute().assertBody("null");
+		a.request("PUT", "/Float?body=").execute().assertBody("null");
+		a.request("PUT", "/Float?body=bad&noTrace=true").execute().assertStatus(400);
+	}
+	@Test
+	public void c07_bodyParam_float() throws Exception {
+		a.request("PUT", "/float?body=1.23").execute().assertBody("1.23");
+		a.request("PUT", "/float?body=-1.23").execute().assertBody("-1.23");
+		a.request("PUT", "/float?body=null").execute().assertBody("0.0");
+		a.request("PUT", "/float?body=").execute().assertBody("0.0");
+		a.request("PUT", "/float?body=bad&noTrace=true").execute().assertStatus(400);
+	}
+	@Test
+	public void c08_bodyParam_Map() throws Exception {
+		a.request("PUT", "/Map?body=(foo=123)").execute().assertBody("{foo:123}");
+		a.request("PUT", "/Map?body=()").execute().assertBody("{}");
+		a.request("PUT", "/Map?body=null").execute().assertBody("null");
+		a.request("PUT", "/Map?body=").execute().assertBody("null");
+		a.request("PUT", "/Map?body=bad&noTrace=true").execute().assertStatus(400);
+	}
+	@Test
+	public void c09_bodyParam_enum() throws Exception {
+		a.request("PUT", "/enum?body=ONE").execute().assertBody("'ONE'");
+		a.request("PUT", "/enum?body=TWO").execute().assertBody("'TWO'");
+		a.request("PUT", "/enum?body=null").execute().assertBody("null");
+		a.request("PUT", "/enum?body=").execute().assertBody("null");
+		a.request("PUT", "/enum?body=bad&noTrace=true").execute().assertStatus(400);
+	}
+	@Test
+	public void c11_bodyParam_Bean() throws Exception {
+		a.request("PUT", "/Bean?body=(f1=a)").execute().assertBody("{f1:'a'}");
+		a.request("PUT", "/Bean?body=()").execute().assertBody("{}");
+		a.request("PUT", "/Bean?body=null").execute().assertBody("null");
+		a.request("PUT", "/Bean?body=").execute().assertBody("null");
+		a.request("PUT", "/Bean?body=bad&noTrace=true").execute().assertStatus(400);
+	}
+	@Test
+	public void c12_bodyParam_InputStream() throws Exception {
+		a.request("PUT", "/InputStream?body=a").execute().assertBody("'a'");
+		a.request("PUT", "/InputStream?body=null").execute().assertBody("'null'");
+		a.request("PUT", "/InputStream?body=").execute().assertBody("''");
+	}
+	@Test
+	public void c13_bodyParam_Reader() throws Exception {
+		a.request("PUT", "/Reader?body=a").execute().assertBody("'a'");
+		a.request("PUT", "/Reader?body=null").execute().assertBody("'null'");
+		a.request("PUT", "/Reader?body=").execute().assertBody("''");
+	}
+	
+	// It's not currently possible to pass in a &body parameter for InputStream/Reader transforms.
+
+	
+	//=================================================================================================================
+	// No serializers or parsers needed when using only streams and readers.
+	//=================================================================================================================
+	
+	@RestResource
+	public static class D {
+		
+		@RestMethod(name=PUT, path="/String")
+		public Reader d01(@Body Reader b) throws Exception {
+			return b;
+		}
+
+		@RestMethod(name=PUT, path="/InputStream")
+		public InputStream d02(@Body InputStream b) throws Exception {
+			return b;
+		}
+
+		@RestMethod(name=PUT, path="/Reader")
+		public Reader d03(@Body Reader b) throws Exception {
+			return b;
+		}
+		
+		@RestMethod(name=PUT, path="/StringTransform")
+		public Reader d04(@Body D04 b) throws Exception {
+			return new StringReader(b.toString());
+		}
+		public static class D04 {
+			private String s;
+			public D04(String in) throws Exception { this.s = in; }
+			@Override public String toString() { return s; }
+		}
+		
+		@RestMethod(name=PUT, path="/InputStreamTransform")
+		public Reader d05(@Body D05 b) throws Exception {
+			return new StringReader(b.toString());
+		}
+		public static class D05 {
+			String s;
+			public D05(InputStream in) throws Exception { this.s = IOUtils.read(in); }
+			@Override public String toString() { return s; }
+		}
+		
+		@RestMethod(name=PUT, path="/ReaderTransform")
+		public Reader d06(@Body D06 b) throws Exception {
+			return new StringReader(b.toString());
+		}
+		public static class D06 {
+			private String s;
+			public D06(Reader in) throws Exception{ this.s = IOUtils.read(in); }
+			@Override public String toString() { return s; }
+		}
+		
+		@RestMethod(name=PUT, path="/StringTransformBodyOnPojo")
+		public Reader d07(D07 b) throws Exception {
+			return new StringReader(b.toString());
+		}
+		@Body
+		public static class D07 {
+			private String s;
+			public D07(String in) throws Exception { this.s = in; }
+			@Override public String toString() { return s; }
+		}
+		
+		@RestMethod(name=PUT, path="/InputStreamTransformBodyOnPojo")
+		public Reader d08(D08 b) throws Exception {
+			return new StringReader(b.toString());
+		}
+		@Body
+		public static class D08 {
+			String s;
+			public D08(InputStream in) throws Exception { this.s = IOUtils.read(in); }
+			@Override public String toString() { return s; }
+		}
+		
+		@RestMethod(name=PUT, path="/ReaderTransformBodyOnPojo")
+		public Reader d09(D09 b) throws Exception {
+			return new StringReader(b.toString());
+		}
+		@Body
+		public static class D09 {
+			private String s;
+			public D09(Reader in) throws Exception{ this.s = IOUtils.read(in); }
+			@Override public String toString() { return s; }
+		}
+	}
+	
+	private static MockRest d = MockRest.create(D.class);
+	
+	@Test
+	public void d01a_noMediaTypes_String() throws Exception {
+		d.request("PUT", "/String").body("a").execute().assertBody("a");
+	}
+	@Test
+	public void d01b_noMediaTypes_String_withContentType() throws Exception {
+		d.request("PUT", "/String").body("a").json().execute().assertBody("a");
+	}
+	@Test
+	public void d02a_noMediaTypes_InputStream() throws Exception {
+		d.request("PUT", "/InputStream").body("a").execute().assertBody("a");
+	}
+	@Test
+	public void d02b_noMediaTypes_InputStream_withContentType() throws Exception {
+		d.request("PUT", "/InputStream").body("a").json().execute().assertBody("a");
+	}
+	@Test
+	public void d03a_noMediaTypes_Reader() throws Exception {
+		d.request("PUT", "/Reader").body("a").execute().assertBody("a");
+	}
+	@Test
+	public void d03b_noMediaTypes_Reader_withContentType() throws Exception {
+		d.request("PUT", "/Reader").body("a").json().execute().assertBody("a");
+	}
+	@Test
+	public void d04a_noMediaTypes_StringTransform() throws Exception {
+		d.request("PUT", "/StringTransform").body("a").execute().assertBody("a");
+	}
+	@Test
+	public void d04b_noMediaTypes_StringTransform_withContentType() throws Exception {
+		d.request("PUT", "/StringTransform?noTrace=true").body("a").json().execute().assertStatus(415);
+	}
+	@Test
+	public void d05a_noMediaTypes_InputStreamTransform() throws Exception {
+		d.request("PUT", "/InputStreamTransform").body("a").execute().assertBody("a");
+	}
+	@Test
+	public void d05b_noMediaTypes_InputStreamTransform_withContentType() throws Exception {
+		d.request("PUT", "/InputStreamTransform").body("a").json().execute().assertBody("a");
+	}
+	@Test
+	public void d06a_noMediaTypes_ReaderTransform() throws Exception {
+		d.request("PUT", "/ReaderTransform").body("a").execute().assertBody("a");
+	}
+	@Test
+	public void d06b_noMediaTypes_ReaderTransform_withContentType() throws Exception {
+		d.request("PUT", "/ReaderTransform").body("a").json().execute().assertBody("a");
+	}
+	@Test
+	public void d07a_noMediaTypes_StringTransformBodyOnPojo() throws Exception {
+		d.request("PUT", "/StringTransformBodyOnPojo").body("a").execute().assertBody("a");
+	}
+	@Test
+	public void d07b_noMediaTypes_StringTransformBodyOnPojo_withContentType() throws Exception {
+		d.request("PUT", "/StringTransformBodyOnPojo?noTrace=true").body("a").json().execute().assertStatus(415);
+	}
+	@Test
+	public void d08a_noMediaTypes_InputStreamTransformBodyOnPojo() throws Exception {
+		d.request("PUT", "/InputStreamTransformBodyOnPojo").body("a").execute().assertBody("a");
+	}
+	@Test
+	public void d08b_noMediaTypes_InputStreamTransformBodyOnPojo_withContentType() throws Exception {
+		d.request("PUT", "/InputStreamTransformBodyOnPojo").body("a").json().execute().assertBody("a");
+	}
+	@Test
+	public void d09a_noMediaTypes_ReaderTransformBodyOnPojo() throws Exception {
+		d.request("PUT", "/ReaderTransformBodyOnPojo").body("a").execute().assertBody("a");
+	}
+	@Test
+	public void d09b_noMediaTypes_ReaderTransformBodyOnPojo_withContentType() throws Exception {
+		d.request("PUT", "/ReaderTransformBodyOnPojo").body("a").json().execute().assertBody("a");
+	}
+	
+	
+	//=================================================================================================================
+	// Complex POJOs
+	//=================================================================================================================
+	
+	@RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class)
+	public static class E {
+	
+		@RestMethod(name=PUT, path="/B")
+		public DTOs.B testPojo1(@Body DTOs.B b) {
+			return b;
+		}
+	
+		@RestMethod(name=PUT, path="/C")
+		public DTOs.C testPojo2(@Body DTOs.C c) {
+			return c;
+		}
+	}
+	
+	private static MockRest e = MockRest.create(E.class);
+	
+	@Test
+	public void e01_complexPojos_B_body() throws Exception {
+		String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...]
+		e.request("PUT", "/B").body(JsonSerializer.DEFAULT_LAX.toString(DTOs.B.INSTANCE)).json().execute().assertBody(expected);
+	}
+	@Test
+	public void e02_complexPojos_B_bodyParam() throws Exception {
+		String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...]
+		e.request("PUT", "/B?body=" + UonSerializer.DEFAULT.serialize(DTOs.B.INSTANCE)).body("a").execute().assertBody(expected);
+	}
+	@Test
+	public void e03_complexPojos_C_body() throws Exception {
+		String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...]
+		e.request("PUT", "/C").body(JsonSerializer.DEFAULT_LAX.toString(DTOs.B.INSTANCE)).json().execute().assertBody(expected);
+	}
+	@Test
+	public void e04_complexPojos_C_bodyParam() throws Exception {
+		String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...]
+		e.request("PUT", "/C?body=" + UonSerializer.DEFAULT.serialize(DTOs.B.INSTANCE)).body("a").execute().assertBody(expected);
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyTest.java
deleted file mode 100644
index 07707bb..0000000
--- a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/BodyTest.java
+++ /dev/null
@@ -1,533 +0,0 @@
-// ***************************************************************************************************************************
-// * 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 static org.apache.juneau.http.HttpMethodName.*;
-import static org.junit.Assert.*;
-
-import java.io.*;
-import java.util.*;
-
-
-import org.apache.juneau.internal.*;
-import org.apache.juneau.json.*;
-import org.apache.juneau.rest.mock.*;
-import org.apache.juneau.rest.testutils.*;
-import org.apache.juneau.uon.*;
-import org.junit.*;
-import org.junit.runners.*;
-
-/**
- * Tests the {@link Body} annotation.
- */
-@SuppressWarnings({"javadoc","serial"})
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class BodyTest {
-	
-	//-----------------------------------------------------------------------------------------------------------------
-	// @Body on parameter
-	//-----------------------------------------------------------------------------------------------------------------
-	
-	@RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class)
-	public static class A {
-		
-		@RestMethod(name=PUT, path="/String")
-		public String a01(@Body String b) {
-			return b;
-		}
-
-		@RestMethod(name=PUT, path="/Integer")
-		public Integer a02(@Body Integer b) {
-			return b;
-		}
-
-		@RestMethod(name=PUT, path="/int")
-		public Integer a03(@Body int b) {
-			return b;
-		}
-
-		@RestMethod(name=PUT, path="/Boolean")
-		public Boolean a04(@Body Boolean b) {
-			return b;
-		}
-
-		@RestMethod(name=PUT, path="/boolean")
-		public Boolean a05(@Body boolean b) {
-			return b;
-		}
-
-		@RestMethod(name=PUT, path="/float")
-		public float a06(@Body float f) {
-			return f;
-		}
-
-		@RestMethod(name=PUT, path="/Float")
-		public Float a07(@Body Float f) {
-			return f;
-		}
-		
-		@RestMethod(name=PUT, path="/Map")
-		public TreeMap<String,Integer> a08(@Body TreeMap<String,Integer> m) {
-			return m;
-		}
-
-		@RestMethod(name=PUT, path="/enum")
-		public TestEnum a09(@Body TestEnum e) {
-			return e;
-		}
-
-		public static class A11 {
-			public String f1;
-		}
-		
-		@RestMethod(name=PUT, path="/bean")
-		public A11 a11(@Body A11 b) {
-			return b;
-		}
-
-		@RestMethod(name=PUT, path="/inputStream")
-		public String a12(@Body InputStream b) throws Exception {
-			return IOUtils.read(b);
-		}
-
-		@RestMethod(name=PUT, path="/reader")
-		public String a13(@Body Reader b) throws Exception {
-			return IOUtils.read(b);
-		}
-
-		public static class A14 {
-			String s;
-			
-			public A14(InputStream in) throws Exception {
-				this.s = IOUtils.read(in);
-			}
-			
-			@Override /* Object */
-			public String toString() {
-				return s;
-			}
-		}
-		
-		@RestMethod(name=PUT, path="/inputStreamTransform")
-		public A14 a14(@Body A14 b) throws Exception {
-			return b;
-		}
-
-		public static class A15 {
-			private String s;
-			
-			public A15(Reader in) throws Exception {
-				this.s = IOUtils.read(in);
-			}
-			
-			@Override /* Object */
-			public String toString() {
-				return s;
-			}
-		}
-		
-		@RestMethod(name=PUT, path="/readerTransform")
-		public A15 a15(@Body A15 b) throws Exception {
-			return b;
-		}
-	}
-	
-	private MockRest a = MockRest.create(A.class);
-	
-	@Test
-	public void a01_onParameter_String() throws Exception {
-		assertEquals("'foo'", a.request("PUT", "/String").body("'foo'").execute().getBodyAsString());
-	}
-	@Test
-	public void a02_onParameter_Integer() throws Exception {
-		assertEquals("123", a.request("PUT", "/Integer").body("123").execute().getBodyAsString());
-	}
-	@Test
-	public void a03_onParameter_int() throws Exception {
-		assertEquals("123", a.request("PUT", "/int").body("123").execute().getBodyAsString());
-	}
-	@Test
-	public void a04_onParameter_Boolean() throws Exception {
-		assertEquals("true", a.request("PUT", "/Boolean").body("true").execute().getBodyAsString());
-	}
-	@Test
-	public void a05_onParameter_boolean() throws Exception {
-		assertEquals("true", a.request("PUT", "/boolean").body("true").execute().getBodyAsString());
-	}
-	@Test
-	public void a06_onParameter_float() throws Exception {
-		assertEquals("1.23", a.request("PUT", "/float").body("1.23").execute().getBodyAsString());
-	}
-	@Test
-	public void a07_onParameter_Float() throws Exception {
-		assertEquals("1.23", a.request("PUT", "/Float").body("1.23").execute().getBodyAsString());
-	}
-	@Test
-	public void a08_onParameter_Map() throws Exception {
-		assertEquals("{foo:123}", a.request("PUT", "/Map").body("{foo:123}").execute().getBodyAsString());
-	}
-	@Test
-	public void a09_onParameter_enum() throws Exception {
-		assertEquals("'ONE'", a.request("PUT", "/enum").body("'ONE'").execute().getBodyAsString());
-	}
-	@Test
-	public void a11_onParameter_bean() throws Exception {
-		assertEquals("{f1:'a'}", a.request("PUT", "/bean").body("{f1:'a'}").execute().getBodyAsString());
-	}
-	@Test
-	public void a12_onParameter_inputStream() throws Exception {
-		assertEquals("'a'", a.request("PUT", "/inputStream").body("a").execute().getBodyAsString());
-	}
-	@Test
-	public void a13_onParameter_reader() throws Exception {
-		assertEquals("'a'", a.request("PUT", "/reader").body("a").execute().getBodyAsString());
-	}
-	@Test
-	public void a14_onParameter_inputStreamTransform() throws Exception {
-		assertEquals("'a'", a.request("PUT", "/inputStreamTransform").body("a").execute().getBodyAsString());
-	}
-	@Test
-	public void a15_onParameter_readerTransform() throws Exception {
-		assertEquals("'a'", a.request("PUT", "/readerTransform").body("a").execute().getBodyAsString());
-	}
-	
-	
-	//-----------------------------------------------------------------------------------------------------------------
-	// @Body on POJO
-	//-----------------------------------------------------------------------------------------------------------------
-	
-	@RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class)
-	public static class B {
-		
-		@Body 
-		public static class B01 {
-			private String val;
-			
-			public B01(String val) {
-				this.val = val;
-			}
-			
-			@Override
-			public String toString() {
-				return val;
-			}
-		}
-		
-		@RestMethod(name=PUT, path="/string")
-		public B01 simple(B01 b) {
-			return b;
-		}
-
-		@Body
-		public static class B02 {
-			public String f1;
-		}
-		
-		@RestMethod(name=PUT, path="/bean")
-		public B02 b02(B02 b) {
-			return b;
-		}
-
-		@Body
-		public static class B03 extends LinkedList<B02> {}
-		
-		@RestMethod(name=PUT, path="/beanList")
-		public B03 b03(B03 b) {
-			return b;
-		}
-		
-		@Body 
-		public static class B04 {
-			String s;
-			
-			public B04(InputStream in) throws Exception {
-				this.s = IOUtils.read(in);
-			}
-			
-			@Override /* Object */
-			public String toString() {
-				return s;
-			}
-		}
-		
-		@RestMethod(name=PUT, path="/inputStreamTransform")
-		public B04 b04(B04 b) throws Exception {
-			return b;
-		}
-		
-		@Body 
-		public static class B05 {
-			private String s;
-			
-			public B05(Reader in) throws Exception {
-				this.s = IOUtils.read(in);
-			}
-			
-			@Override /* Object */
-			public String toString() {
-				return s;
-			}
-		}
-		
-		@RestMethod(name=PUT, path="/readerTransform")
-		public B05 b05(B05 b) throws Exception {
-			return b;
-		}
-	}
-	
-	private MockRest b = MockRest.create(B.class);
-
-	@Test
-	public void b01_onPojo_string() throws Exception {
-		assertEquals("'foo'", b.request("PUT", "/string").body("'foo'").execute().getBodyAsString());
-	}
-	@Test
-	public void b02_onPojo_bean() throws Exception {
-		assertEquals("{f1:'a'}", b.request("PUT", "/bean").body("{f1:'a'}").execute().getBodyAsString());
-	}
-	@Test
-	public void b03_onPojo_beanList() throws Exception {
-		assertEquals("[{f1:'a'}]", b.request("PUT", "/beanList").body("[{f1:'a'}]").execute().getBodyAsString());
-	}
-	@Test
-	public void b04_onPojo_inputStream() throws Exception {
-		assertEquals("'a'", b.request("PUT", "/inputStreamTransform").body("a").execute().getBodyAsString());
-	}
-	@Test
-	public void b05_onPojo_reader() throws Exception {
-		assertEquals("'a'", b.request("PUT", "/readerTransform").body("a").execute().getBodyAsString());
-	}
-
-	
-	//-----------------------------------------------------------------------------------------------------------------
-	// Basic tests using @Body parameter
-	//-----------------------------------------------------------------------------------------------------------------
-
-	public void c01_bodyParam_String() throws Exception {
-		assertEquals("'foo'", a.request("PUT", "/String?body=foo").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/String?body=null").execute().getBodyAsString());
-		assertEquals("''", a.request("PUT", "/String?body=").execute().getBodyAsString());
-	}
-	@Test
-	public void c02_bodyParam_Integer() throws Exception {
-		assertEquals("123", a.request("PUT", "/Integer?body=123").execute().getBodyAsString());
-		assertEquals("-123", a.request("PUT", "/Integer?body=-123").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/Integer?body=null").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/Integer?body=").execute().getBodyAsString());
-		assertEquals(400, a.request("PUT", "/Integer?body=bad&noTrace=true").execute().getStatus());
-	}
-	@Test
-	public void c03_bodyParam_int() throws Exception {
-		assertEquals("123", a.request("PUT", "/int?body=123").execute().getBodyAsString());
-		assertEquals("-123", a.request("PUT", "/int?body=-123").execute().getBodyAsString());
-		assertEquals("0", a.request("PUT", "/int?body=null").execute().getBodyAsString());
-		assertEquals("0", a.request("PUT", "/int?body=").execute().getBodyAsString());
-		assertEquals(400, a.request("PUT", "/int?body=bad&noTrace=true").execute().getStatus());
-	}
-	@Test
-	public void c04_bodyParam_Boolean() throws Exception {
-		assertEquals("true", a.request("PUT", "/Boolean?body=true").execute().getBodyAsString());
-		assertEquals("false", a.request("PUT", "/Boolean?body=false").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/Boolean?body=null").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/Boolean?body=").execute().getBodyAsString());
-		assertEquals(400, a.request("PUT", "/Boolean?body=bad&noTrace=true").execute().getStatus());
-	}
-	@Test
-	public void c05_bodyParam_boolean() throws Exception {
-		assertEquals("true", a.request("PUT", "/boolean?body=true").execute().getBodyAsString());
-		assertEquals("false", a.request("PUT", "/boolean?body=false").execute().getBodyAsString());
-		assertEquals("false", a.request("PUT", "/boolean?body=null").execute().getBodyAsString());
-		assertEquals("false", a.request("PUT", "/boolean?body=").execute().getBodyAsString());
-		assertEquals(400, a.request("PUT", "/boolean?body=bad&noTrace=true").execute().getStatus());
-	}
-	@Test
-	public void c06_bodyParam_Float() throws Exception {
-		assertEquals("1.23", a.request("PUT", "/Float?body=1.23").execute().getBodyAsString());
-		assertEquals("-1.23", a.request("PUT", "/Float?body=-1.23").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/Float?body=null").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/Float?body=").execute().getBodyAsString());
-		assertEquals(400, a.request("PUT", "/Float?body=bad&noTrace=true").execute().getStatus());
-	}
-	@Test
-	public void c07_bodyParam_float() throws Exception {
-		assertEquals("1.23", a.request("PUT", "/float?body=1.23").execute().getBodyAsString());
-		assertEquals("-1.23", a.request("PUT", "/float?body=-1.23").execute().getBodyAsString());
-		assertEquals("0.0", a.request("PUT", "/float?body=null").execute().getBodyAsString());
-		assertEquals("0.0", a.request("PUT", "/float?body=").execute().getBodyAsString());
-		assertEquals(400, a.request("PUT", "/float?body=bad&noTrace=true").execute().getStatus());
-	}
-	@Test
-	public void c08_bodyParam_Map() throws Exception {
-		assertEquals("{foo:123}", a.request("PUT", "/Map?body=(foo=123)").execute().getBodyAsString());
-		assertEquals("{}", a.request("PUT", "/Map?body=()").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/Map?body=null").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/Map?body=").execute().getBodyAsString());
-		assertEquals(400, a.request("PUT", "/Map?body=bad&noTrace=true").execute().getStatus());
-	}
-	@Test
-	public void c09_bodyParam_enum() throws Exception {
-		assertEquals("'ONE'", a.request("PUT", "/enum?body=ONE").execute().getBodyAsString());
-		assertEquals("'TWO'", a.request("PUT", "/enum?body=TWO").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/enum?body=null").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/enum?body=").execute().getBodyAsString());
-		assertEquals(400, a.request("PUT", "/enum?body=bad&noTrace=true").execute().getStatus());
-	}
-	@Test
-	public void c11_bodyParam_bean() throws Exception {
-		assertEquals("{f1:'a'}", a.request("PUT", "/bean?body=(f1=a)").execute().getBodyAsString());
-		assertEquals("{}", a.request("PUT", "/bean?body=()").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/bean?body=null").execute().getBodyAsString());
-		assertEquals("null", a.request("PUT", "/bean?body=").execute().getBodyAsString());
-		assertEquals(400, a.request("PUT", "/bean?body=bad&noTrace=true").execute().getStatus());
-	}
-	@Test
-	public void c12_bodyParam_inputStream() throws Exception {
-		assertEquals("'a'", a.request("PUT", "/inputStream?body=a").execute().getBodyAsString());
-		assertEquals("'null'", a.request("PUT", "/inputStream?body=null").execute().getBodyAsString());
-		assertEquals("''", a.request("PUT", "/inputStream?body=").execute().getBodyAsString());
-	}
-	@Test
-	public void c13_bodyParam_reader() throws Exception {
-		assertEquals("'a'", a.request("PUT", "/reader?body=a").execute().getBodyAsString());
-		assertEquals("'null'", a.request("PUT", "/reader?body=null").execute().getBodyAsString());
-		assertEquals("''", a.request("PUT", "/reader?body=").execute().getBodyAsString());
-	}
-	@Test
-	public void c14_bodyParam_inputStreamTransform() throws Exception {
-		assertEquals("'a'", a.request("PUT", "/inputStreamTransform?body=a").execute().getBodyAsString());
-		assertEquals("'null'", a.request("PUT", "/inputStreamTransform?body=null").execute().getBodyAsString());
-		assertEquals("''", a.request("PUT", "/inputStreamTransform?body=").execute().getBodyAsString());
-	}
-	@Test
-	public void c15_bodyParam_readerTransform() throws Exception {
-		assertEquals("'a'", a.request("PUT", "/readerTransform?body=a").execute().getBodyAsString());
-		assertEquals("'null'", a.request("PUT", "/readerTransform?body=null").execute().getBodyAsString());
-		assertEquals("''", a.request("PUT", "/readerTransform?body=").execute().getBodyAsString());
-	}
-
-	
-	//-----------------------------------------------------------------------------------------------------------------
-	// No serializers or parsers needed when using only streams and readers.
-	//-----------------------------------------------------------------------------------------------------------------
-	
-	@RestResource
-	public static class D {
-		
-		@RestMethod(name=PUT, path="/inputStream")
-		public InputStream d01(@Body InputStream b) throws Exception {
-			return b;
-		}
-
-		@RestMethod(name=PUT, path="/reader")
-		public Reader d02(@Body Reader b) throws Exception {
-			return b;
-		}
-
-		public static class D03 {
-			String s;
-			
-			public D03(InputStream in) throws Exception {
-				this.s = IOUtils.read(in);
-			}
-			
-			@Override /* Object */
-			public String toString() {
-				return s;
-			}
-		}
-		
-		@RestMethod(name=PUT, path="/inputStreamTransform")
-		public Reader d03(@Body D03 b) throws Exception {
-			return new StringReader(b.toString());
-		}
-
-		public static class D04 {
-			private String s;
-			
-			public D04(Reader in) throws Exception {
-				this.s = IOUtils.read(in);
-			}
-			
-			@Override /* Object */
-			public String toString() {
-				return s;
-			}
-		}
-		
-		@RestMethod(name=PUT, path="/readerTransform")
-		public Reader d04(@Body D04 b) throws Exception {
-			return new StringReader(b.toString());
-		}
-	}
-	
-	private MockRest d = MockRest.create(D.class);
-	
-	@Test
-	public void d01_noMediaTypes_inputStream() throws Exception {
-		assertEquals("a", d.request("PUT", "/inputStream").body("a").execute().getBodyAsString());
-	}
-	@Test
-	public void d02_noMediaTypes_reader() throws Exception {
-		assertEquals("a", d.request("PUT", "/reader").body("a").execute().getBodyAsString());
-	}
-	@Test
-	public void d03_noMediaTypes_inputStreamTransform() throws Exception {
-		assertEquals("a", d.request("PUT", "/inputStreamTransform").body("a").execute().getBodyAsString());
-	}
-	@Test
-	public void d04_noMediaTypes_readerTransform() throws Exception {
-		assertEquals("a", d.request("PUT", "/readerTransform").body("a").execute().getBodyAsString());
-	}
-	
-	
-	//-----------------------------------------------------------------------------------------------------------------
-	// Complex POJOs
-	//-----------------------------------------------------------------------------------------------------------------
-	
-	@RestResource(serializers=JsonSerializer.Simple.class, parsers=JsonParser.class)
-	public static class E {
-	
-		@RestMethod(name=PUT, path="/B")
-		public DTOs.B testPojo1(@Body DTOs.B b) {
-			return b;
-		}
-	
-		@RestMethod(name=PUT, path="/C")
-		public DTOs.C testPojo2(@Body DTOs.C c) {
-			return c;
-		}
-	}
-	
-	private MockRest e = MockRest.create(E.class);
-	
-	@Test
-	public void e01_complexPojos_B_body() throws Exception {
-		String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...]
-		assertEquals(expected, e.request("PUT", "/B").body(JsonSerializer.DEFAULT_LAX.toString(DTOs.B.INSTANCE)).execute().getBodyAsString());
-	}
-	@Test
-	public void e02_complexPojos_B_bodyParam() throws Exception {
-		String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...]
-		assertEquals(expected, e.request("PUT", "/B?body=" + UonSerializer.DEFAULT.serialize(DTOs.B.INSTANCE)).body("a").execute().getBodyAsString());
-	}
-	@Test
-	public void e03_complexPojos_C_body() throws Exception {
-		String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...]
-		assertEquals(expected, e.request("PUT", "/C").body(JsonSerializer.DEFAULT_LAX.toString(DTOs.B.INSTANCE)).execute().getBodyAsString());
-	}
-	@Test
-	public void e04_complexPojos_C_bodyParam() throws Exception {
-		String expected = "{f01:['a','b'],f02:['c','d'],f03:[1,2],f04:[3,4],f05:[['e','f'],['g','h']],f06:[['i','j'],['k','l']],f07:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f08:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f09:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f10:[[{a:'a',b:1,c:true}],[{a:'a',b:1,c:true}]],f11:['a','b'],f12:['c','d'],f13:[1,2],f14:[3,4],f15:[['e','f'],['g','h']],f16:[['i','j'],['k','l']],f17:[{a:'a',b:1,c:true},{a:'a',b:1,c:true}],f18:[{a:'a',b:1,c:true},{a:'a',b:1,c:true} [...]
-		assertEquals(expected, e.request("PUT", "/C?body=" + UonSerializer.DEFAULT.serialize(DTOs.B.INSTANCE)).body("a").execute().getBodyAsString());
-	}
-}
diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/DefaultContentTypesTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/DefaultContentTypesTest.java
new file mode 100644
index 0000000..3a9907b
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/DefaultContentTypesTest.java
@@ -0,0 +1,384 @@
+// ***************************************************************************************************************************
+// * 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 static org.apache.juneau.http.HttpMethodName.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.rest.mock.*;
+import org.apache.juneau.serializer.*;
+import org.junit.*;
+import org.junit.runners.*;
+
+
+/**
+ * Tests related to @RestRequest(defaultRequestHeaders).
+ */
+@SuppressWarnings({"javadoc"})
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class DefaultContentTypesTest {
+	
+	//=================================================================================================================
+	// Setup classes
+	//=================================================================================================================
+
+	public static class DummyParser extends ReaderParser {
+
+		String name;
+
+		DummyParser(PropertyStore ps, String name, String...consumes) {
+			super(ps, consumes);
+			this.name = name;
+		}
+
+		@Override /* Parser */
+		public ReaderParserSession createSession(ParserSessionArgs args) {
+			return new ReaderParserSession(args) {
+				@Override /* ParserSession */
+				@SuppressWarnings("unchecked")
+				protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception {
+					return (T)name;
+				}
+			};
+		}
+	}
+
+	public static class DummySerializer extends WriterSerializer {
+
+		String name;
+
+		DummySerializer(PropertyStore ps, String name, String produces) {
+			super(ps, produces, null);
+			this.name = name;
+		}
+
+		@Override /* Serializer */
+		public WriterSerializerSession createSession(SerializerSessionArgs args) {
+			return new WriterSerializerSession(args) {
+				@Override /* SerializerSession */
+				protected void doSerialize(SerializerPipe out, Object o) throws Exception {
+					out.getWriter().write(name + "/" + o);
+				}
+			};
+		}
+	}
+	
+	public static class P1 extends DummyParser { public P1(PropertyStore ps) {super(ps, "p1", "text/p1");}}
+	public static class P2 extends DummyParser { public P2(PropertyStore ps) {super(ps, "p2", "text/p2");}}
+	public static class P3 extends DummyParser { public P3(PropertyStore ps) {super(ps, "p3", "text/p3");}}
+	public static class S1 extends DummySerializer { public S1(PropertyStore ps) {super(ps, "s1", "text/s1");}}
+	public static class S2 extends DummySerializer { public S2(PropertyStore ps) {super(ps, "s2", "text/s2");}}
+	public static class S3 extends DummySerializer { public S3(PropertyStore ps) {super(ps, "s3", "text/s3");}}
+	
+
+	//====================================================================================================
+	// Test that default Accept and Content-Type headers on servlet annotation are picked up.
+	//====================================================================================================
+
+	@RestResource(
+		defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "},
+		parsers={P1.class,P2.class}, serializers={S1.class,S2.class}
+	)
+	public static class A {
+		@RestMethod(name=PUT)
+		public String a01(@Body String in) {
+			return in;
+		}
+	}
+	
+	private static MockRest a = MockRest.create(A.class);
+	
+	@Test
+	public void a01_defaultHeadersOnServletAnnotation_valid() throws Exception {
+		a.request("PUT", "/").execute().assertBody("s2/p2");
+		a.request("PUT", "/").accept("text/s1").execute().assertBody("s1/p2");
+		a.request("PUT", "/").contentType("text/p1").execute().assertBody("s2/p1");
+		a.request("PUT", "/").accept("text/s1").contentType("text/p1").execute().assertBody("s1/p1");
+		a.request("PUT", "/").accept("text/s2").execute().assertBody("s2/p2");
+		a.request("PUT", "/").contentType("text/p2").execute().assertBody("s2/p2");
+		a.request("PUT", "/").accept("text/s2").contentType("text/p2").execute().assertBody("s2/p2");
+	}
+
+	@Test
+	public void a02_defaultHeadersOnServletAnnotation_invalid() throws Exception {
+		a.request("PUT", "?noTrace=true").accept("text/s3").execute().assertStatus(406).assertBodyContains("Unsupported media-type in request header 'Accept': 'text/s3'");
+		a.request("PUT", "?noTrace=true").contentType("text/p3").execute().assertStatus(415).assertBodyContains("Unsupported media-type in request header 'Content-Type': 'text/p3'");
+		a.request("PUT", "?noTrace=true").accept("text/s3").contentType("text/p3").execute().assertStatus(415).assertBodyContains("Unsupported media-type in request header 'Content-Type': 'text/p3'");
+	}
+
+	//====================================================================================================
+	// Test that default Accept and Content-Type headers on servlet annotation are picked up
+	// when @RestMethod.parsers/serializers annotations are used.
+	//====================================================================================================
+	
+	@RestResource(
+		defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "},
+		parsers={P1.class,P2.class}, serializers={S1.class,S2.class}
+	)
+	public static class B {
+		@RestMethod(name=PUT, parsers=P3.class, serializers=S3.class)
+		public String b(@Body String in) {
+			return in;
+		}
+	}
+	
+	private static MockRest b = MockRest.create(B.class);
+	
+	@Test
+	public void b01_restMethodWithParsersSerializers_valid() throws Exception {
+		b.request("PUT", "/").accept("text/s3").contentType("text/p3").execute().assertBody("s3/p3");
+	}
+
+	@Test
+	public void b02_restMethodWithParsersSerializers_invalid() throws Exception {
+		b.request("PUT", "?noTrace=true").execute()
+			.assertStatus(415)
+			.assertBodyContains( 
+				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
+				"Supported media-types: ['text/p3']"
+			);
+		b.request("PUT", "?noTrace=true").accept("text/s1").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
+				"Supported media-types: ['text/p3']"
+			);
+		b.request("PUT", "?noTrace=true").contentType("text/p1").execute()
+			.assertStatus(415)
+			.assertBodyContains( 
+				"Unsupported media-type in request header 'Content-Type': 'text/p1'",
+				"Supported media-types: ['text/p3']"
+			);
+		b.request("PUT", "?noTrace=true").accept("text/s1").contentType("text/p1").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p1'",
+				"Supported media-types: ['text/p3']"
+			);
+		b.request("PUT", "?noTrace=true").accept("text/s1").accept("text/s2").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
+				"Supported media-types: ['text/p3']"
+			);
+		b.request("PUT", "?noTrace=true").accept("text/s1").contentType("text/p2").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
+				"Supported media-types: ['text/p3']"
+			);
+		b.request("PUT", "?noTrace=true").accept("text/s2").contentType("text/p2").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
+				"Supported media-types: ['text/p3']"
+			);
+		b.request("PUT", "?noTrace=true").accept("text/s3").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
+				"Supported media-types: ['text/p3']"
+			);
+		b.request("PUT", "?noTrace=true").contentType("text/p3").execute()
+			.assertStatus(406)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Accept': 'text/s2'",
+				"Supported media-types: ['text/s3']"
+			);
+	}
+	
+	
+	//====================================================================================================
+	// Test that default Accept and Content-Type headers on servlet annotation are picked up
+	// when @RestMethod.addParsers/addSerializers annotations are used.
+	//====================================================================================================
+	
+	@RestResource(
+		defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "},
+		parsers={P1.class,P2.class}, serializers={S1.class,S2.class}
+	)
+	public static class C {
+		@RestMethod(name=PUT, parsers=P3.class, serializers=S3.class, inherit="SERIALIZERS,PARSERS")
+		public String c(@Body String in) {
+			return in;
+		}
+	}
+	
+	private static MockRest c = MockRest.create(C.class);
+
+	@Test
+	public void c01_restMethodAddParsersSerializersInherit() throws Exception {
+		c.request("PUT", "/").execute().assertBody("s2/p2");
+		c.request("PUT", "/").accept("text/s1").execute().assertBody("s1/p2");
+		c.request("PUT", "/").contentType("text/p1").execute().assertBody("s2/p1");
+		c.request("PUT", "/").accept("text/s1").contentType("text/p1").execute().assertBody("s1/p1");
+		c.request("PUT", "/").accept("text/s2").execute().assertBody("s2/p2");
+		c.request("PUT", "/").contentType("text/p2").execute().assertBody("s2/p2");
+		c.request("PUT", "/").accept("text/s2").contentType("text/p2").execute().assertBody("s2/p2");
+		c.request("PUT", "/").accept("text/s3").execute().assertBody("s3/p2");
+		c.request("PUT", "/").contentType("text/p3").execute().assertBody("s2/p3");
+		c.request("PUT", "/").accept("text/s3").contentType("text/p3").execute().assertBody("s3/p3");
+	}
+	
+	@Test
+	public void c02_restMethodAddParsersSerializersInherit_invalid() throws Exception {
+		c.request("PUT", "?noTrace=true").contentType("text/p4").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p4'",
+				"Supported media-types: ['text/p3','text/p1','text/p2']"
+			);
+		c.request("PUT", "?noTrace=true").accept("text/s4").execute()
+			.assertStatus(406)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Accept': 'text/s4'",
+				"Supported media-types: ['text/s3','text/s1','text/s2']"
+			);
+	}
+
+
+	//====================================================================================================
+	// Various Accept incantations.
+	//====================================================================================================
+	@RestResource(
+		defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "},
+		parsers={P1.class,P2.class}, serializers={S1.class,S2.class}
+	)
+	public static class D {
+		@RestMethod(name=PUT)
+		public String d(@Body String in) {
+			return in;
+		}
+	}
+	
+	private static MockRest d = MockRest.create(D.class);
+	
+	@Test
+	public void d01_accept_valid() throws Exception {
+		// "*/*" should match the first serializer, not the default serializer.
+		d.request("PUT", "/").contentType("text/p1").accept("*/*").execute().assertBody("s1/p1");
+		// "text/*" should match the first serializer, not the default serializer.
+		d.request("PUT", "/").contentType("text/p1").accept("text/*").execute().assertBody("s1/p1");
+		d.request("PUT", "/").contentType("text/p1").accept("bad/*,text/*").execute().assertBody("s1/p1");
+		d.request("PUT", "/").contentType("text/p1").accept("text/*,bad/*").execute().assertBody("s1/p1");
+		d.request("PUT", "/").contentType("text/p1").accept("text/s1;q=0.5,text/s2").execute().assertBody("s2/p1");
+		d.request("PUT", "/").contentType("text/p1").accept("text/s1,text/s2;q=0.5").execute().assertBody("s1/p1");
+	}
+	@Test
+	public void d02_accept_invalid() throws Exception {
+		d.request("PUT", "?noTrace=true").contentType("text/p1").accept("bad/*").execute()
+			.assertStatus(406)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Accept': 'bad/*'",
+				"Supported media-types: ['text/s1','text/s2']"
+			);
+	}
+
+	
+	//====================================================================================================
+	// Test that default Accept and Content-Type headers on method annotation are picked up
+	// when @RestMethod.parsers/serializers annotations are used.
+	//====================================================================================================
+	@RestResource(
+		defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "},
+		parsers={P1.class,P2.class}, serializers={S1.class,S2.class}
+	)
+	public static class E {
+		@RestMethod(name=PUT, defaultRequestHeaders={"Accept: text/s3","Content-Type: text/p3"}, parsers=P3.class, serializers=S3.class)
+		public String e(@Body String in) {
+			return in;
+		}
+	}
+	
+	private static MockRest e = MockRest.create(E.class);
+
+	@Test
+	public void e01_restMethodParserSerializerAnnotations_valid() throws Exception {
+		e.request("PUT", "/").execute().assertBody("s3/p3");
+		e.request("PUT", "/").accept("text/s3").execute().assertBody("s3/p3");
+		e.request("PUT", "/").contentType("text/p3").execute().assertBody("s3/p3");
+		e.request("PUT", "/").accept("text/s3").contentType("text/p3").execute().assertBody("s3/p3");
+	}
+	@Test
+	public void e02_restMethodParserSerializerAnnotations_invalid() throws Exception {
+		e.request("PUT", "?noTrace=true").accept("text/s1").execute()
+			.assertStatus(406)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Accept': 'text/s1'",
+				"Supported media-types: ['text/s3']"
+			);
+		e.request("PUT", "?noTrace=true").contentType("text/p1").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p1'",
+				"Supported media-types: ['text/p3']"
+			);
+		e.request("PUT", "?noTrace=true").accept("text/s1").contentType("text/p1").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p1'",
+				"Supported media-types: ['text/p3']"
+			);
+		e.request("PUT", "?noTrace=true").accept("text/s2").execute()
+			.assertStatus(406)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Accept': 'text/s2'",
+				"Supported media-types: ['text/s3']"
+			);
+		e.request("PUT", "?noTrace=true").contentType("text/p2").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
+				"Supported media-types: ['text/p3']"
+			);
+		e.request("PUT", "?noTrace=true").accept("text/s2").contentType("text/p2").execute()
+			.assertStatus(415)
+			.assertBodyContains(
+				"Unsupported media-type in request header 'Content-Type': 'text/p2'",
+				"Supported media-types: ['text/p3']"
+			);
+	}
+
+	
+	//====================================================================================================
+	// Test that default Accept and Content-Type headers on method annotation are picked up
+	// 	when @RestMethod.addParsers/addSerializers annotations are used.
+	//====================================================================================================
+	@RestResource(
+		defaultRequestHeaders={" Accept : text/s2 "," Content-Type : text/p2 "},
+		parsers={P1.class,P2.class}, serializers={S1.class,S2.class}
+	)
+	public static class F {
+		@RestMethod(name=PUT, defaultRequestHeaders={"Accept: text/s3","Content-Type: text/p3"}, parsers=P3.class, serializers=S3.class, inherit="SERIALIZERS,PARSERS")
+		public String f(@Body String in) {
+			return in;
+		}
+	}
+	
+	private static MockRest f = MockRest.create(F.class);
+
+	@Test
+	public void f01_restMethodAddParsersSerializersAnnotations_valid() throws Exception {
+		f.request("PUT", "/").execute().assertBody("s3/p3");
+		f.request("PUT", "/").accept("text/s1").execute().assertBody("s1/p3");
+		f.request("PUT", "/").contentType("text/p1").execute().assertBody("s3/p1");
+		f.request("PUT", "/").accept("text/s1").contentType("text/p1").execute().assertBody("s1/p1");
+		f.request("PUT", "/").accept("text/s2").execute().assertBody("s2/p3");
+		f.request("PUT", "/").contentType("text/p2").execute().assertBody("s3/p2");
+		f.request("PUT", "/").accept("text/s2").contentType("text/p2").execute().assertBody("s2/p2");
+		f.request("PUT", "/").accept("text/s3").execute().assertBody("s3/p3");
+		f.request("PUT", "/").contentType("text/p3").execute().assertBody("s3/p3");
+		f.request("PUT", "/").accept("text/s3").contentType("text/p3").execute().assertBody("s3/p3");
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/StatusCodesTest.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/StatusCodesTest.java
new file mode 100644
index 0000000..546844a
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/StatusCodesTest.java
@@ -0,0 +1,48 @@
+// ***************************************************************************************************************************
+// * 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 static org.apache.juneau.http.HttpMethodName.*;
+
+import java.io.*;
+
+import org.apache.juneau.rest.mock.*;
+import org.junit.*;
+
+/**
+ * Validates that the correct status codes are returned on REST requests.
+ */
+@SuppressWarnings("javadoc")
+public class StatusCodesTest {
+
+	//=================================================================================================================
+	// OK
+	//=================================================================================================================
+	
+	@RestResource
+	public static class A {
+		@RestMethod(name=PUT)
+		public Reader a01(@Body String b) {
+			return new StringReader(b);
+		}
+	}
+	
+	private static MockRest a = MockRest.create(A.class);
+	
+	@Test
+	public void a01a_OK() throws Exception {
+		a.request("PUT", "/").body("foo").execute().assertStatus(200);
+	}
+	
+	// TODO - Test all the status codes
+}

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

Mime
View raw message