Break up RestRequest into different functional classes.

Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/321f6bde
Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/321f6bde
Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/321f6bde

Branch: refs/heads/master
Commit: 321f6bdeef10b2d5cabb3a51c14dc92349aa7cb3
Parents: fa4736b
Author: JamesBognar <[email protected]>
Authored: Sun May 7 16:16:08 2017 -0400
Committer: JamesBognar <[email protected]>
Committed: Sun May 7 16:16:08 2017 -0400

----------------------------------------------------------------------
 .../urlencoding/UrlEncodingParserTest.java      |    8 +-
 .../java/org/apache/juneau/http/HttpMethod.java |   73 +
 .../org/apache/juneau/internal/WrappedMap.java  |   94 ++
 .../juneau/urlencoding/UrlEncodingParser.java   |    5 +-
 juneau-core/src/main/javadoc/overview.html      |   34 +-
 .../examples/rest/MethodExampleResource.java    |   16 +-
 .../rest/addressbook/AddressBookResource.java   |    4 +-
 .../rest/test/CallbackStringsResource.java      |    8 +-
 .../juneau/rest/test/OnPreCallResource.java     |    2 +-
 .../rest/test/OverlappingMethodsResource.java   |    8 +-
 .../apache/juneau/rest/test/ParamsResource.java |   36 +-
 .../juneau/rest/test/RestClient2Resource.java   |    2 +-
 .../java/org/apache/juneau/rest/CallMethod.java |   32 +-
 .../org/apache/juneau/rest/RequestBody.java     |  431 ++++++
 .../org/apache/juneau/rest/RequestFormData.java |  271 ++++
 .../org/apache/juneau/rest/RequestHeaders.java  |  675 ++++++++++
 .../apache/juneau/rest/RequestPathParams.java   |  137 ++
 .../org/apache/juneau/rest/RequestQuery.java    |  287 ++++
 .../org/apache/juneau/rest/RestContext.java     |    2 +-
 .../java/org/apache/juneau/rest/RestParam.java  |  978 ++++++++++++++
 .../org/apache/juneau/rest/RestParamType.java   |   48 +
 .../org/apache/juneau/rest/RestRequest.java     | 1247 +++---------------
 .../apache/juneau/rest/annotation/FormData.java |    2 +-
 .../juneau/rest/annotation/HasFormData.java     |    2 +-
 .../apache/juneau/rest/annotation/HasQuery.java |    2 +-
 .../apache/juneau/rest/annotation/Query.java    |    2 +-
 .../juneau/rest/converters/Introspectable.java  |    4 +-
 .../juneau/rest/converters/Queryable.java       |   17 +-
 .../juneau/rest/converters/Traversable.java     |    2 +-
 .../rest/remoteable/RemoteableServlet.java      |    4 +-
 .../juneau/rest/response/DefaultHandler.java    |    4 +-
 .../org/apache/juneau/rest/vars/RequestVar.java |    4 +-
 32 files changed, 3258 insertions(+), 1183 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingParserTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingParserTest.java
 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingParserTest.java
index d23eca3..3ae3679 100755
--- 
a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingParserTest.java
+++ 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingParserTest.java
@@ -606,7 +606,7 @@ public class UrlEncodingParserTest {
                Map<String,String[]> m;
 
                String s = 
"?f1=,()=&f2a=$b(true)&f2b=true&f3a=$n(123)&f3b=123&f4=$s(foo)";
-               m = p.parseIntoSimpleMap(s);
+               m = p.parseIntoSimpleMap(s, null);
                assertEquals(",()=", m.get("f1")[0]);
                assertEquals("$b(true)", m.get("f2a")[0]);
                assertEquals("true", m.get("f2b")[0]);
@@ -615,12 +615,12 @@ public class UrlEncodingParserTest {
                assertEquals("$s(foo)", m.get("f4")[0]);
 
                s = "f1=v1&=";
-               m = p.parseIntoSimpleMap(s);
+               m = p.parseIntoSimpleMap(s, null);
                assertEquals("v1", m.get("f1")[0]);
                assertEquals("", m.get("")[0]);
 
                s = "f1=v1&f2&f3";
-               m = p.parseIntoSimpleMap(s);
+               m = p.parseIntoSimpleMap(s, null);
                assertEquals("v1", m.get("f1")[0]);
                assertTrue(m.containsKey("f2"));
                assertTrue(m.containsKey("f3"));
@@ -637,7 +637,7 @@ public class UrlEncodingParserTest {
                Map<String,String[]> m;
 
                String s = "?f1&f1&f2&f2=abc&f2=def&f2";
-               m = p.parseIntoSimpleMap(s);
+               m = p.parseIntoSimpleMap(s, null);
                assertObjectEquals("{f1:null,f2:['abc','def']}", m);
        }
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-core/src/main/java/org/apache/juneau/http/HttpMethod.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/http/HttpMethod.java 
b/juneau-core/src/main/java/org/apache/juneau/http/HttpMethod.java
new file mode 100644
index 0000000..b6ccf3b
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/http/HttpMethod.java
@@ -0,0 +1,73 @@
+// 
***************************************************************************************************************************
+// * 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.http;
+
+import java.util.*;
+
+/**
+ * Represents valid HTTP 1.1 method names per the <a class='doclink' 
href='https://www.ietf.org/rfc/rfc2616.txt'>RFC 2616</a> spec.
+ */
+public enum HttpMethod {
+
+       /** <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2'>OPTIONS</a>
 */
+       OPTIONS,
+
+       /** <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3'>GET</a> */
+       GET,
+
+       /** <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4'>HEAD</a> */
+       HEAD,
+
+       /** <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5'>POST</a> */
+       POST,
+
+       /** <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6'>PUT</a> */
+       PUT,
+
+       /** <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7'>DELETE</a> 
*/
+       DELETE,
+
+       /** <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8'>TRACE</a> 
*/
+       TRACE,
+
+       /** <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.9'>CONNECT</a>
 */
+       CONNECT,
+
+       /** A non-standard value. */
+       OTHER;
+
+       private static final Map<String,HttpMethod> cache = new 
TreeMap<String,HttpMethod>(String.CASE_INSENSITIVE_ORDER);
+       static {
+               cache.put("OPTIONS", OPTIONS);
+               cache.put("GET", GET);
+               cache.put("HEAD", HEAD);
+               cache.put("POST", POST);
+               cache.put("PUT", PUT);
+               cache.put("DELETE", DELETE);
+               cache.put("TRACE", TRACE);
+               cache.put("CONNECT", CONNECT);
+       }
+
+       /**
+        * Returns the enum for the specified key.
+        * <p>
+        * Case is ignored.
+        *
+        * @param key The HTTP method name.
+        * @return The HttpMethod enum, or {@link #OTHER} if it's not a 
standard method name.
+        */
+       public static HttpMethod forString(String key) {
+               HttpMethod m = cache.get(key);
+               return m == null ? OTHER : m;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-core/src/main/java/org/apache/juneau/internal/WrappedMap.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/internal/WrappedMap.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/WrappedMap.java
new file mode 100644
index 0000000..d1f0adf
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/WrappedMap.java
@@ -0,0 +1,94 @@
+// 
***************************************************************************************************************************
+// * 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.internal;
+
+import java.util.*;
+
+/**
+ * Wraps an existing map inside an extensible interface so that convenience 
methods can be added to the subclass.
+ * @param <K> The key type.
+ * @param <V> The value type.
+ */
+public class WrappedMap<K,V> implements Map<K,V> {
+
+       private final Map<K,V> inner;
+
+       /**
+        * Constructor.
+        *
+        * @param inner The inner map.
+        */
+       protected WrappedMap(Map<K,V> inner) {
+               this.inner = inner;
+       }
+
+       @Override /* Map */
+       public void clear() {
+               inner.clear();
+       }
+
+       @Override
+       public boolean containsKey(Object key) {
+               return inner.containsKey(key);
+       }
+
+       @Override /* Map */
+       public boolean containsValue(Object value) {
+               return inner.containsValue(value);
+       }
+
+       @Override /* Map */
+       public Set<java.util.Map.Entry<K,V>> entrySet() {
+               return inner.entrySet();
+       }
+
+       @Override /* Map */
+       public V get(Object key) {
+               return inner.get(key);
+       }
+
+       @Override /* Map */
+       public boolean isEmpty() {
+               return inner.isEmpty();
+       }
+
+       @Override /* Map */
+       public Set<K> keySet() {
+               return inner.keySet();
+       }
+
+       @Override /* Map */
+       public V put(K key, V value) {
+               return inner.put(key, value);
+       }
+
+       @Override /* Map */
+       public void putAll(Map<? extends K,? extends V> m) {
+               inner.putAll(m);
+       }
+
+       @Override /* Map */
+       public V remove(Object key) {
+               return inner.remove(key);
+       }
+
+       @Override /* Map */
+       public int size() {
+               return inner.size();
+       }
+
+       @Override /* Map */
+       public Collection<V> values() {
+               return inner.values();
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
index af4881e..cb9f19c 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
@@ -333,12 +333,13 @@ public class UrlEncodingParser extends UonParser {
         * Parse a URL query string into a simple map of key/value pairs.
         *
         * @param qs The query string to parse.
+        * @param map The map to parse into.  If <jk>null</jk>, then a new 
{@link TreeMap} will be used.
         * @return A sorted {@link TreeMap} of query string entries.
         * @throws Exception
         */
-       public Map<String,String[]> parseIntoSimpleMap(String qs) throws 
Exception {
+       public Map<String,String[]> parseIntoSimpleMap(String qs, 
Map<String,String[]> map) throws Exception {
 
-               Map<String,String[]> m = new TreeMap<String,String[]>();
+               Map<String,String[]> m = map == null ? new 
TreeMap<String,String[]>() : map;
 
                if (StringUtils.isEmpty(qs))
                        return m;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-core/src/main/javadoc/overview.html
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/javadoc/overview.html 
b/juneau-core/src/main/javadoc/overview.html
index 566c169..44ef436 100644
--- a/juneau-core/src/main/javadoc/overview.html
+++ b/juneau-core/src/main/javadoc/overview.html
@@ -5911,29 +5911,29 @@
                <h6 class='topic'>org.apache.juneau</h6>
                <ul class='spaced-list'>
                        <li>New package:  {@link org.apache.juneau.http}.
-                       <ul>
-                               <li>{@link org.apache.juneau.http.Accept}
-                               <li>{@link 
org.apache.juneau.http.AcceptEncoding}
-                               <li>{@link org.apache.juneau.http.ContentType}
-                       </ul>
                        <li>Support for dynamic beans.  See {@link 
org.apache.juneau.annotation.BeanProperty#name() @BeanProperty.name()}.
                        <li>New doc: <a class='doclink' 
href='#Core.JacksonComparison'>2.12 - Comparison with Jackson</a>
                </ul>
 
                <h6 class='topic'>org.apache.juneau.rest</h6>
                <ul class='spaced-list'>
-                       <li>The following object types can now be specified as 
unannotated arguments on REST Java methods:
-                       <ul>
-                               <li>{@link org.apache.juneau.http.Accept}
-                               <li>{@link 
org.apache.juneau.http.AcceptEncoding}
-                               <li>{@link org.apache.juneau.http.ContentType}
-                       </ul>
-                       <li>New methods on {@link 
org.apache.juneau.rest.RestRequest}:
-                       <ul>
-                               <li>{@link 
org.apache.juneau.rest.RestRequest#getAcceptHeader() getAcceptHeader()}
-                               <li>{@link 
org.apache.juneau.rest.RestRequest#getAcceptEncodingHeader() 
getAcceptEncodingHeader()}
-                               <li>{@link 
org.apache.juneau.rest.RestRequest#getContentTypeHeader() 
getContentTypeHeader()}
-                       </ul>
+                       <li>The {@link org.apache.juneau.rest.RestRequest} 
class functionality has been broken up into the following
+                               functional pieces to reduce its complexity:
+                               <ul>
+                                       <li>{@link 
org.apache.juneau.rest.RestRequest#getBody()} - The request body.
+                                       <li>{@link 
org.apache.juneau.rest.RestRequest#getHeaders()} - The request headers.
+                                       <li>{@link 
org.apache.juneau.rest.RestRequest#getQuery()} - The request query parameters.
+                                       <li>{@link 
org.apache.juneau.rest.RestRequest#getFormData()} - The request form data 
parameters.
+                                       <li>{@link 
org.apache.juneau.rest.RestRequest#getPathParams()} - The path variables.
+                               </ul>   
+                               The following classes have been introduced:
+                               <ul>
+                                       <li>{@link 
org.apache.juneau.rest.RequestBody}
+                                       <li>{@link 
org.apache.juneau.rest.RequestHeaders}
+                                       <li>{@link 
org.apache.juneau.rest.RequestQuery}
+                                       <li>{@link 
org.apache.juneau.rest.RequestFormData}
+                                       <li>{@link 
org.apache.juneau.rest.RequestPathParams}
+                               </ul>                                           
                </ul>
 
                <h6 class='topic'>org.apache.juneau.rest.client</h6>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/MethodExampleResource.java
----------------------------------------------------------------------
diff --git 
a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/MethodExampleResource.java
 
b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/MethodExampleResource.java
index 1d06a5a..ba6ce35 100644
--- 
a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/MethodExampleResource.java
+++ 
b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/MethodExampleResource.java
@@ -62,21 +62,23 @@ public class MethodExampleResource extends Resource {
                String method = req.getMethod();
 
                // Attributes (from URL pattern variables)
-               String a1 = req.getPathParameter("a1", String.class);
-               int a2 = req.getPathParameter("a2", int.class);
-               UUID a3 = req.getPathParameter("a3", UUID.class);
+               RequestPathParams path = req.getPathParams();
+               String a1 = path.get("a1", String.class);
+               int a2 = path.get("a2", int.class);
+               UUID a3 = path.get("a3", UUID.class);
 
                // Optional GET parameters
-               int p1 = req.getQueryParameter("p1", 0, int.class);
-               String p2 = req.getQueryParameter("p2", String.class);
-               UUID p3 = req.getQueryParameter("p3", UUID.class);
+               RequestQuery query = req.getQuery();
+               int p1 = query.get("p1", 0, int.class);
+               String p2 = query.get("p2", String.class);
+               UUID p3 = query.get("p3", UUID.class);
 
                // URL pattern post-match
                String remainder = req.getPathRemainder();
 
                // Headers
                String lang = req.getHeader("Accept-Language");
-               int doNotTrack = req.getHeader("DNT", int.class);
+               int doNotTrack = req.getHeaders().get("DNT", int.class);
 
                // Send back a simple String response
                String output = String.format(

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
----------------------------------------------------------------------
diff --git 
a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
 
b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
index ab9ef57..122d949 100644
--- 
a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
+++ 
b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
@@ -209,7 +209,7 @@ public class AddressBookResource extends ResourceJena {
                        String pathRemainder = req.getPathRemainder();
                        PojoRest r = new PojoRest(p);
                        ClassMeta<?> cm = r.getClassMeta(pathRemainder);
-                       Object in = req.getBody(cm);
+                       Object in = req.getBody().asType(cm);
                        r.put(pathRemainder, in);
                        return "PUT successful";
                } catch (Exception e) {
@@ -230,7 +230,7 @@ public class AddressBookResource extends ResourceJena {
                        String pathInfo = req.getPathInfo();
                        PojoRest r = new PojoRest(a);
                        ClassMeta<?> cm = r.getClassMeta(pathInfo);
-                       Object in = req.getBody(cm);
+                       Object in = req.getBody().asType(cm);
                        r.put(pathInfo, in);
                        return "PUT successful";
                } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/CallbackStringsResource.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/CallbackStringsResource.java
 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/CallbackStringsResource.java
index 8b873af..74038b3 100644
--- 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/CallbackStringsResource.java
+++ 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/CallbackStringsResource.java
@@ -32,7 +32,7 @@ public class CallbackStringsResource extends 
RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="GET", path="/")
        public ObjectMap test1(RestRequest req) throws Exception {
-               return new ObjectMap().append("method","GET").append("headers", 
getFooHeaders(req)).append("content", req.getBodyAsString());
+               return new ObjectMap().append("method","GET").append("headers", 
getFooHeaders(req)).append("content", req.getBody().asString());
        }
 
        
//====================================================================================================
@@ -40,14 +40,14 @@ public class CallbackStringsResource extends 
RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="PUT", path="/")
        public ObjectMap testCharsetOnResponse(RestRequest req) throws 
Exception {
-               return new ObjectMap().append("method","PUT").append("headers", 
getFooHeaders(req)).append("content", req.getBodyAsString());
+               return new ObjectMap().append("method","PUT").append("headers", 
getFooHeaders(req)).append("content", req.getBody().asString());
        }
 
        private Map<String,Object> getFooHeaders(RestRequest req) {
                Map<String,Object> m = new TreeMap<String,Object>();
-               for (Map.Entry<String,Object> e : req.getHeaders().entrySet())
+               for (Map.Entry<String,String[]> e : req.getHeaders().entrySet())
                        if (e.getKey().startsWith("Foo-"))
-                               m.put(e.getKey(), e.getValue());
+                               m.put(e.getKey(), e.getValue()[0]);
                return m;
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OnPreCallResource.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OnPreCallResource.java
 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OnPreCallResource.java
index 46e20a6..9479ab4 100644
--- 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OnPreCallResource.java
+++ 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OnPreCallResource.java
@@ -85,6 +85,6 @@ public class OnPreCallResource extends RestServlet {
        public String testPropertiesOverriddenProgrammatically(RestRequest req, 
@Properties ObjectMap properties) throws Exception {
                properties.put("p3", "pp3");
                properties.put("p4", "pp4");
-               return req.getBody(String.class);
+               return req.getBody().asType(String.class);
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java
 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java
index 6bfef72..c5dfce1 100644
--- 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java
+++ 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/OverlappingMethodsResource.java
@@ -45,14 +45,14 @@ public class OverlappingMethodsResource extends 
RestServletDefault {
        public static class Test1Guard extends RestGuard {
                @Override /* RestGuard */
                public boolean isRequestAllowed(RestRequest req) {
-                       return req.getQueryParameter("t1","").equals("1");
+                       return req.getQuery("t1","").equals("1");
                }
        }
 
        public static class Test2Guard extends RestGuard {
                @Override /* RestGuard */
                public boolean isRequestAllowed(RestRequest req) {
-                       return req.getQueryParameter("t2","").equals("2");
+                       return req.getQuery("t2","").equals("2");
                }
        }
 
@@ -77,14 +77,14 @@ public class OverlappingMethodsResource extends 
RestServletDefault {
        public static class Test3aMatcher extends RestMatcher {
                @Override /* RestMatcher */
                public boolean matches(RestRequest req) {
-                       return req.getQueryParameter("t1","").equals("1");
+                       return req.getQuery("t1","").equals("1");
                }
        }
 
        public static class Test3bMatcher extends RestMatcher {
                @Override /* RestMatcher */
                public boolean matches(RestRequest req) {
-                       return req.getQueryParameter("t2","").equals("2");
+                       return req.getQuery("t2","").equals("2");
                }
        }
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
index d9424e7..0c29593 100644
--- 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
+++ 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ParamsResource.java
@@ -112,7 +112,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="GET", path="/testParamGet/*")
        public String testParamGet(RestRequest req, @Query("p1") String p1, 
@Query("p2") int p2) throws Exception {
-               return 
"p1=["+p1+","+req.getQueryParameter("p1")+","+req.getQueryParameter("p1", 
String.class)+"],p2=["+p2+","+req.getQueryParameter("p2")+","+req.getQueryParameter("p2",
 int.class)+"]";
+               RequestQuery q = req.getQuery();
+               return "p1=["+p1+","+req.getQuery("p1")+","+q.get("p1", 
String.class)+"],p2=["+p2+","+q.getFirst("p2")+","+q.get("p2", int.class)+"]";
        }
 
        
//====================================================================================================
@@ -120,7 +121,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="POST", path="/testParamPost/*")
        public String testParamPost(RestRequest req, @FormData("p1") String p1, 
@FormData("p2") int p2) throws Exception {
-               return 
"p1=["+p1+","+req.getFormDataParameter("p1")+","+req.getFormDataParameter("p1", 
String.class)+"],p2=["+p2+","+req.getFormDataParameter("p2")+","+req.getFormDataParameter("p2",
 int.class)+"]";
+               RequestFormData f = req.getFormData();
+               return "p1=["+p1+","+req.getFormData("p1")+","+f.get("p1", 
String.class)+"],p2=["+p2+","+req.getFormData("p2")+","+f.get("p2", 
int.class)+"]";
        }
 
        
//====================================================================================================
@@ -128,7 +130,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="GET", path="/testQParamGet/*")
        public String testQParamGet(RestRequest req, @Query("p1") String p1, 
@Query("p2") int p2) throws Exception {
-               return 
"p1=["+p1+","+req.getQueryParameter("p1")+","+req.getQueryParameter("p1", 
String.class)+"],p2=["+p2+","+req.getQueryParameter("p2")+","+req.getQueryParameter("p2",
 int.class)+"]";
+               RequestQuery q = req.getQuery();
+               return "p1=["+p1+","+req.getQuery("p1")+","+q.get("p1", 
String.class)+"],p2=["+p2+","+q.getFirst("p2")+","+q.get("p2", int.class)+"]";
        }
 
        
//====================================================================================================
@@ -136,7 +139,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="POST", path="/testQParamPost/*")
        public String testQParamPost(RestRequest req, @Query("p1") String p1, 
@Query("p2") int p2) throws Exception {
-               return 
"p1=["+p1+","+req.getQueryParameter("p1")+","+req.getQueryParameter("p1", 
String.class)+"],p2=["+p2+","+req.getQueryParameter("p2")+","+req.getQueryParameter("p2",
 int.class)+"]";
+               RequestQuery q = req.getQuery();
+               return "p1=["+p1+","+req.getQuery("p1")+","+q.get("p1", 
String.class)+"],p2=["+p2+","+q.getFirst("p2")+","+q.get("p2", int.class)+"]";
        }
 
        
//====================================================================================================
@@ -144,7 +148,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="GET", path="/testPlainParamGet/*")
        public String testPlainParamGet(RestRequest req, 
@Query(value="p1",format="PLAIN") String p1) throws Exception {
-               return 
"p1=["+p1+","+req.getQueryParameter("p1")+","+req.getQueryParameter("p1", 
String.class)+"]";
+               RequestQuery q = req.getQuery();
+               return "p1=["+p1+","+req.getQuery("p1")+","+q.get("p1", 
String.class)+"]";
        }
 
        
//====================================================================================================
@@ -152,7 +157,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="POST", path="/testPlainParamPost/*")
        public String testPlainParamPost(RestRequest req, 
@FormData(value="p1",format="PLAIN") String p1) throws Exception {
-               return 
"p1=["+p1+","+req.getFormDataParameter("p1")+","+req.getFormDataParameter("p1", 
String.class)+"]";
+               RequestFormData f = req.getFormData();
+               return "p1=["+p1+","+req.getFormData("p1")+","+f.get("p1", 
String.class)+"]";
        }
 
        
//====================================================================================================
@@ -160,7 +166,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="GET", path="/testPlainQParamGet/*")
        public String testPlainQParamGet(RestRequest req, 
@Query(value="p1",format="PLAIN") String p1) throws Exception {
-               return 
"p1=["+p1+","+req.getQueryParameter("p1")+","+req.getQueryParameter("p1", 
String.class)+"]";
+               RequestQuery q = req.getQuery();
+               return "p1=["+p1+","+req.getQuery("p1")+","+q.get("p1", 
String.class)+"]";
        }
 
        
//====================================================================================================
@@ -168,7 +175,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="POST", path="/testPlainQParamPost/*")
        public String testPlainQParamPost(RestRequest req, 
@Query(value="p1",format="PLAIN") String p1) throws Exception {
-               return 
"p1=["+p1+","+req.getQueryParameter("p1")+","+req.getQueryParameter("p1", 
String.class)+"]";
+               RequestQuery q = req.getQuery();
+               return "p1=["+p1+","+req.getQuery("p1")+","+q.get("p1", 
String.class)+"]";
        }
 
        
//====================================================================================================
@@ -176,7 +184,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="GET", path="/testHasParamGet/*")
        public String testHasParamGet(RestRequest req, @HasQuery("p1") boolean 
p1, @HasQuery("p2") Boolean p2) throws Exception {
-               return 
"p1=["+p1+","+req.hasQueryParameter("p1")+"],p2=["+p2+","+req.hasQueryParameter("p2")+"]";
+               RequestQuery q = req.getQuery();
+               return 
"p1=["+p1+","+q.containsKey("p1")+"],p2=["+p2+","+q.containsKey("p2")+"]";
        }
 
        
//====================================================================================================
@@ -184,7 +193,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="POST", path="/testHasParamPost/*")
        public String testHasParamPost(RestRequest req, @HasFormData("p1") 
boolean p1, @HasFormData("p2") Boolean p2) throws Exception {
-               return 
"p1=["+p1+","+req.hasFormDataParameter("p1")+"],p2=["+p2+","+req.hasFormDataParameter("p2")+"]";
+               RequestFormData f = req.getFormData();
+               return 
"p1=["+p1+","+f.containsKey("p1")+"],p2=["+p2+","+f.containsKey("p2")+"]";
        }
 
        
//====================================================================================================
@@ -192,7 +202,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="GET", path="/testHasQParamGet/*")
        public String testHasQParamGet(RestRequest req, @HasQuery("p1") boolean 
p1, @HasQuery("p2") Boolean p2) throws Exception {
-               return 
"p1=["+p1+","+req.hasQueryParameter("p1")+"],p2=["+p2+","+req.hasQueryParameter("p2")+"]";
+               RequestQuery q = req.getQuery();
+               return 
"p1=["+p1+","+q.containsKey("p1")+"],p2=["+p2+","+q.containsKey("p2")+"]";
        }
 
        
//====================================================================================================
@@ -200,7 +211,8 @@ public class ParamsResource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="POST", path="/testHasQParamPost/*")
        public String testHasQParamPost_post(RestRequest req, @HasQuery("p1") 
boolean p1, @HasQuery("p2") Boolean p2) throws Exception {
-               return 
"p1=["+p1+","+req.hasQueryParameter("p1")+"],p2=["+p2+","+req.hasQueryParameter("p2")+"]";
+               RequestQuery q = req.getQuery();
+               return 
"p1=["+p1+","+q.containsKey("p1")+"],p2=["+p2+","+q.containsKey("p2")+"]";
        }
 
        
//====================================================================================================

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/RestClient2Resource.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/RestClient2Resource.java
 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/RestClient2Resource.java
index 671f12c..5aa04b9 100644
--- 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/RestClient2Resource.java
+++ 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/RestClient2Resource.java
@@ -31,6 +31,6 @@ public class RestClient2Resource extends RestServletDefault {
        
//====================================================================================================
        @RestMethod(name="POST", path="/")
        public Reader test1(RestRequest req) throws Exception {
-               return new StringReader(req.getBodyAsString());
+               return new StringReader(req.getBody().asString());
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java 
b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
index 6fb9a08..df39005 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
@@ -425,32 +425,32 @@ class CallMethod implements Comparable<CallMethod>  {
                        switch(paramType) {
                                case REQ:        return req;
                                case RES:        return res;
-                               case PATH:       return 
req.getPathParameter(name, type);
-                               case BODY:       return req.getBody(type);
-                               case HEADER:     return req.getHeader(name, 
type);
+                               case PATH:       return 
req.getPathParams().get(name, type);
+                               case BODY:       return 
req.getBody().asType(type);
+                               case HEADER:     return 
req.getHeaders().get(name, type);
                                case METHOD:     return req.getMethod();
                                case FORMDATA: {
                                        if (multiPart)
-                                               return 
req.getFormDataParameters(name, type);
+                                               return 
req.getFormData().getAll(name, type);
                                        if (plainParams)
-                                               return 
session.convertToType(req.getFormDataParameter(name), 
session.getClassMeta(type));
-                                       return req.getFormDataParameter(name, 
type);
+                                               return 
session.convertToType(req.getFormData(name), session.getClassMeta(type));
+                                       return req.getFormData().get(name, 
type);
                                }
                                case QUERY: {
                                        if (multiPart)
-                                               return 
req.getQueryParameters(name, type);
+                                               return 
req.getQuery().getAll(name, type);
                                        if (plainParams)
-                                               return 
session.convertToType(req.getQueryParameter(name), session.getClassMeta(type));
-                                       return req.getQueryParameter(name, 
type);
+                                               return 
session.convertToType(req.getQuery(name), session.getClassMeta(type));
+                                       return req.getQuery().get(name, type);
                                }
-                               case HASFORMDATA:   return 
session.convertToType(req.hasFormDataParameter(name), 
session.getClassMeta(type));
-                               case HASQUERY:      return 
session.convertToType(req.hasQueryParameter(name), session.getClassMeta(type));
+                               case HASFORMDATA:   return 
session.convertToType(req.getFormData().containsKey(name), 
session.getClassMeta(type));
+                               case HASQUERY:      return 
session.convertToType(req.getQuery().containsKey(name), 
session.getClassMeta(type));
                                case PATHREMAINDER: return 
req.getPathRemainder();
                                case PROPS:         return res.getProperties();
                                case MESSAGES:      return 
req.getResourceBundle();
-                               case ACCEPT:        return 
req.getAcceptHeader();
-                               case ACCEPTENCODING:return 
req.getAcceptEncodingHeader();
-                               case CONTENTTYPE:   return 
req.getContentTypeHeader();
+                               case ACCEPT:        return 
req.getHeaders().getAccept();
+                               case ACCEPTENCODING:return 
req.getHeaders().getAcceptEncoding();
+                               case CONTENTTYPE:   return 
req.getHeaders().getContentType();
                                default:            return null;
                        }
                }
@@ -937,9 +937,9 @@ class CallMethod implements Comparable<CallMethod>  {
                                                if ("path".equals(prefix))
                                                        return 
req.getPathParameter(remainder);
                                                if ("query".equals(prefix))
-                                                       return 
req.getQueryParameter(remainder);
+                                                       return 
req.getQuery(remainder);
                                                if ("formData".equals(prefix))
-                                                       return 
req.getFormDataParameter(remainder);
+                                                       return 
req.getFormData(remainder);
                                                if ("header".equals(prefix))
                                                        return 
req.getHeader(remainder);
                                        }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/RequestBody.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RequestBody.java 
b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestBody.java
new file mode 100644
index 0000000..1b509b4
--- /dev/null
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestBody.java
@@ -0,0 +1,431 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                
                                              *
+// *                                                                           
                                              *
+// *  http://www.apache.org/licenses/LICENSE-2.0                               
                                              *
+// *                                                                           
                                              *
+// * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the 
License.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau.rest;
+
+import static javax.servlet.http.HttpServletResponse.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.servlet.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Contains the body of the HTTP request.
+ */
+@SuppressWarnings("unchecked")
+public class RequestBody {
+
+       private byte[] body;
+       private final RestRequest req;
+       private EncoderGroup encoders;
+       private Encoder encoder;
+       private ParserGroup parsers;
+       private UrlEncodingParser urlEncodingParser;
+       private RequestHeaders headers;
+       private BeanSession beanSession;
+       private int contentLength = 0;
+
+       RequestBody(RestRequest req) {
+               this.req = req;
+       }
+
+       RequestBody setEncoders(EncoderGroup encoders) {
+               this.encoders = encoders;
+               return this;
+       }
+
+       RequestBody setParsers(ParserGroup parsers) {
+               this.parsers = parsers;
+               return this;
+       }
+
+       RequestBody setHeaders(RequestHeaders headers) {
+               this.headers = headers;
+               return this;
+       }
+
+       RequestBody setUrlEncodingParser(UrlEncodingParser urlEncodingParser) {
+               this.urlEncodingParser = urlEncodingParser;
+               return this;
+       }
+
+       RequestBody setBeanSession(BeanSession beanSession) {
+               this.beanSession = beanSession;
+               return this;
+       }
+
+       @SuppressWarnings("hiding")
+       RequestBody load(byte[] body) {
+               this.body = body;
+               return this;
+       }
+
+       boolean isLoaded() {
+               return body != null;
+       }
+
+       /**
+        * Reads the input from the HTTP request as JSON, XML, or HTML and 
converts the input to a POJO.
+        * <p>
+        * If {@code allowHeaderParams} init parameter is <jk>true</jk>, then 
first looks for {@code &body=xxx} in the URL 
+        * query string.
+        * <p>
+        * If type is <jk>null</jk> or <code>Object.<jk>class</jk></code>, then 
the actual type will be determined 
+        * automatically based on the following input:
+        * <table class='styled'>
+        *      <tr><th>Type</th><th>JSON input</th><th>XML 
input</th><th>Return type</th></tr>
+        *      <tr>
+        *              <td>object</td>
+        *              <td><js>"{...}"</js></td>
+        *              
<td><code><xt>&lt;object&gt;</xt>...<xt>&lt;/object&gt;</xt></code><br><code><xt>&lt;x</xt>
 <xa>type</xa>=<xs>'object'</xs><xt>&gt;</xt>...<xt>&lt;/x&gt;</xt></code></td>
+        *              <td>{@link ObjectMap}</td>
+        *      </tr>
+        *      <tr>
+        *              <td>array</td>
+        *              <td><js>"[...]"</js></td>
+        *              
<td><code><xt>&lt;array&gt;</xt>...<xt>&lt;/array&gt;</xt></code><br><code><xt>&lt;x</xt>
 <xa>type</xa>=<xs>'array'</xs><xt>&gt;</xt>...<xt>&lt;/x&gt;</xt></code></td>
+        *              <td>{@link ObjectList}</td>
+        *      </tr>
+        *      <tr>
+        *              <td>string</td>
+        *              <td><js>"'...'"</js></td>
+        *              
<td><code><xt>&lt;string&gt;</xt>...<xt>&lt;/string&gt;</xt></code><br><code><xt>&lt;x</xt>
 <xa>type</xa>=<xs>'string'</xs><xt>&gt;</xt>...<xt>&lt;/x&gt;</xt></code></td>
+        *              <td>{@link String}</td>
+        *      </tr>
+        *      <tr>
+        *              <td>number</td>
+        *              <td><code>123</code></td>
+        *              
<td><code><xt>&lt;number&gt;</xt>123<xt>&lt;/number&gt;</xt></code><br><code><xt>&lt;x</xt>
 <xa>type</xa>=<xs>'number'</xs><xt>&gt;</xt>...<xt>&lt;/x&gt;</xt></code></td>
+        *              <td>{@link Number}</td>
+        *      </tr>
+        *      <tr>
+        *              <td>boolean</td>
+        *              <td><jk>true</jk></td>
+        *              
<td><code><xt>&lt;boolean&gt;</xt>true<xt>&lt;/boolean&gt;</xt></code><br><code><xt>&lt;x</xt>
 <xa>type</xa>=<xs>'boolean'</xs><xt>&gt;</xt>...<xt>&lt;/x&gt;</xt></code></td>
+        *              <td>{@link Boolean}</td>
+        *      </tr>
+        *      <tr>
+        *              <td>null</td>
+        *              <td><jk>null</jk> or blank</td>
+        *              <td><code><xt>&lt;null/&gt;</xt></code> or 
blank<br><code><xt>&lt;x</xt> 
<xa>type</xa>=<xs>'null'</xs><xt>/&gt;</xt></code></td>
+        *              <td><jk>null</jk></td>
+        *      </tr>
+        * </table>
+        * <p>
+        * Refer to <a class="doclink" 
href="../../../../overview-summary.html#Core.PojoCategories">POJO 
Categories</a> for 
+        * a complete definition of supported POJOs.
+        * <p>
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode'>
+        *      <jc>// Parse into an integer.</jc>
+        *      <jk>int</jk> body = req.getBody(<jk>int</jk>.<jk>class</jk>);
+        *
+        *      <jc>// Parse into an int array.</jc>
+        *      <jk>int</jk>[] body = 
req.getBody(<jk>int</jk>[].<jk>class</jk>);
+
+        *      <jc>// Parse into a bean.</jc>
+        *      MyBean body = req.getBody(MyBean.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a linked-list of objects.</jc>
+        *      List body = req.getBody(LinkedList.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map of object keys/values.</jc>
+        *      Map body = req.getBody(TreeMap.<jk>class</jk>);
+        * </p>
+        *
+        * @param type The class type to instantiate.
+        * @param <T> The class type to instantiate.
+        * @return The input parsed to a POJO.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        * @throws ParseException If the input contains a syntax error or is 
malformed for the requested {@code Accept} 
+        * header or is not valid for the specified type.
+        */
+       public <T> T asType(Class<T> type) throws IOException, ParseException {
+               return parse(beanSession.getClassMeta(type));
+       }
+
+       /**
+        * Reads the input from the HTTP request as JSON, XML, or HTML and 
converts the input to a POJO.
+        * <p>
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode'>
+        *      <jc>// Parse into a linked-list of strings.</jc>
+        *      List&lt;String&gt; body = 
req.getBody(LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a linked-list of linked-lists of strings.</jc>
+        *      List&lt;List&lt;String&gt;&gt; body = 
req.getBody(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, 
String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map of string keys/values.</jc>
+        *      Map&lt;String,String&gt; body = 
req.getBody(TreeMap.<jk>class</jk>, String.<jk>class</jk>, 
String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map containing string keys and values of 
lists containing beans.</jc>
+        *      Map&lt;String,List&lt;MyBean&gt;&gt; body = 
req.getBody(TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, 
MyBean.<jk>class</jk>);
+        * </p>
+        *
+        * @param type The type of object to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType}, 
+        *      {@link GenericArrayType}
+        * @param args The type arguments of the class if it's a collection or 
map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType}, 
+        *      {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @param <T> The class type to instantiate.
+        * @return The input parsed to a POJO.
+        */
+       public <T> T asType(Type type, Type...args) {
+               return (T)parse(beanSession.getClassMeta(type, args));
+       }
+
+       /**
+        * Returns the HTTP body content as a plain string.
+        * <p>
+        * If {@code allowHeaderParams} init parameter is true, then first 
looks for {@code &body=xxx} in the URL query 
+        * string.
+        *
+        * @return The incoming input from the connection as a plain string.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        */
+       public String asString() throws IOException {
+               if (body == null)
+                       body = IOUtils.readBytes(getInputStream(), 1024);
+               return new String(body, IOUtils.UTF8);
+       }
+
+       /**
+        * Returns the HTTP body content as a simple hexadecimal character 
string.
+        *
+        * @return The incoming input from the connection as a plain string.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        */
+       public String asHex() throws IOException {
+               if (body == null)
+                       body = IOUtils.readBytes(getInputStream(), 1024);
+               return StringUtils.toHex(body);
+       }
+
+       /**
+        * Returns the HTTP body content as a {@link Reader}.
+        * <p>
+        * If {@code allowHeaderParams} init parameter is true, then first 
looks for {@code &body=xxx} in the URL query 
+        * string.
+        * <p>
+        * Automatically handles GZipped input streams.
+        * @return The body contents as a reader.
+        * @throws IOException
+        */
+       public BufferedReader getReader() throws IOException {
+               Reader r = getUnbufferedReader();
+               if (r instanceof BufferedReader)
+                       return (BufferedReader)r;
+               int len = req.getContentLength();
+               int buffSize = len <= 0 ? 8192 : Math.max(len, 8192);
+               return new BufferedReader(r, buffSize);
+       }
+
+       /**
+        * Same as {@link #getReader()}, but doesn't encapsulate the result in 
a {@link BufferedReader};
+        *
+        * @return An unbuffered reader.
+        * @throws IOException
+        */
+       protected Reader getUnbufferedReader() throws IOException {
+               if (body != null)
+                       return new CharSequenceReader(new String(body, 
IOUtils.UTF8));
+               return new InputStreamReader(getInputStream(), 
req.getCharacterEncoding());
+       }
+
+       /**
+        * Returns the HTTP body content as an {@link InputStream}.
+        * <p>
+        * Automatically handles GZipped input streams.
+        *
+        * @return The negotiated input stream.
+        * @throws IOException If any error occurred while trying to get the 
input stream or wrap it
+        *      in the GZIP wrapper.
+        */
+       public ServletInputStream getInputStream() throws IOException {
+
+               if (body != null)
+                       return new ServletInputStream2(body);
+
+               Encoder enc = getEncoder();
+
+               ServletInputStream is = req.getRawInputStream();
+               if (enc != null) {
+                       final InputStream is2 = enc.getInputStream(is);
+                       return new ServletInputStream2(is2);
+               }
+               return is;
+       }
+
+       /**
+        * Returns the parser and media type matching the request 
<code>Content-Type</code> header.
+        *
+        * @return The parser matching the request <code>Content-Type</code> 
header, or <jk>null</jk>
+        *      if no matching parser was found.
+        * Includes the matching media type.
+        */
+       public ParserMatch getParserMatch() {
+               MediaType mediaType = headers.getContentType();
+               if (mediaType == null) {
+                       if (body != null)
+                               mediaType = MediaType.UON;
+                       else
+                               mediaType = MediaType.JSON;
+               }
+               ParserMatch pm = parsers.getParserMatch(mediaType);
+
+               // If no patching parser for URL-encoding, use the one defined 
on the servlet.
+               if (pm == null && mediaType.equals(MediaType.URLENCODING))
+                       pm = new ParserMatch(MediaType.URLENCODING, 
urlEncodingParser);
+
+               return pm;
+       }
+
+       /**
+        * Returns the parser matching the request <code>Content-Type</code> 
header.
+        *
+        * @return The parser matching the request <code>Content-Type</code> 
header, or <jk>null</jk>
+        *      if no matching parser was found.
+        */
+       public Parser getParser() {
+               ParserMatch pm = getParserMatch();
+               return (pm == null ? null : pm.getParser());
+       }
+
+       /**
+        * Returns the reader parser matching the request 
<code>Content-Type</code> header.
+        *
+        * @return The reader parser matching the request 
<code>Content-Type</code> header, or <jk>null</jk>
+        *      if no matching reader parser was found, or the matching parser 
was an input stream parser.
+        */
+       public ReaderParser getReaderParser() {
+               Parser p = getParser();
+               if (p != null && p.isReaderParser())
+                       return (ReaderParser)p;
+               return null;
+       }
+
+       /* Workhorse method */
+       private <T> T parse(ClassMeta<T> cm) throws RestException {
+
+               try {
+                       if (cm.isReader())
+                               return (T)getReader();
+
+                       if (cm.isInputStream())
+                               return (T)getInputStream();
+
+                       TimeZone timeZone = headers.getTimeZone();
+                       Locale locale = req.getLocale();
+                       ParserMatch pm = getParserMatch();
+
+                       if (pm != null) {
+                               Parser p = pm.getParser();
+                               MediaType mediaType = pm.getMediaType();
+                               try {
+                                       req.getProperties().append("mediaType", 
mediaType).append("characterEncoding", req.getCharacterEncoding());
+                                       if (! p.isReaderParser()) {
+                                               InputStreamParser p2 = 
(InputStreamParser)p;
+                                               ParserSession session = 
p2.createSession(getInputStream(), req.getProperties(), req.getJavaMethod(), 
req.getContext().getResource(), locale, timeZone, mediaType);
+                                               return p2.parseSession(session, 
cm);
+                                       }
+                                       ReaderParser p2 = (ReaderParser)p;
+                                       ParserSession session = 
p2.createSession(getUnbufferedReader(), req.getProperties(), 
req.getJavaMethod(), req.getContext().getResource(), locale, timeZone, 
mediaType);
+                                       return p2.parseSession(session, cm);
+                               } catch (ParseException e) {
+                                       throw new RestException(SC_BAD_REQUEST,
+                                               "Could not convert request body 
content to class type ''{0}'' using parser ''{1}''.",
+                                               cm, p.getClass().getName()
+                                       ).initCause(e);
+                               }
+                       }
+
+                       throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE,
+                               "Unsupported media-type in request header 
''Content-Type'': ''{0}''\n\tSupported media-types: {1}",
+                               headers.getContentType(), 
req.getParserGroup().getSupportedMediaTypes()
+                       );
+
+               } catch (IOException e) {
+                       throw new RestException(SC_INTERNAL_SERVER_ERROR,
+                               "I/O exception occurred while attempting to 
handle request ''{0}''.",
+                               req.getDescription()
+                       ).initCause(e);
+               }
+       }
+
+       private Encoder getEncoder() {
+               if (encoder == null) {
+                       String ce = req.getHeader("content-encoding");
+                       if (! (ce == null || ce.isEmpty())) {
+                               ce = ce.trim();
+                               encoder = encoders.getEncoder(ce);
+                               if (encoder == null)
+                                       throw new 
RestException(SC_UNSUPPORTED_MEDIA_TYPE,
+                                               "Unsupported encoding in 
request header ''Content-Encoding'': ''{0}''\n\tSupported codings: {1}",
+                                               
req.getHeader("content-encoding"), encoders.getSupportedEncodings()
+                                       );
+                       }
+
+                       if (encoder != null)
+                               contentLength = -1;
+               }
+               // Note that if this is the identity encoder, we want to return 
null
+               // so that we don't needlessly wrap the input stream.
+               if (encoder == IdentityEncoder.INSTANCE)
+                       return null;
+               return encoder;
+       }
+
+       /**
+        * Returns the content length of the body.
+        * @return The content length of the body in bytes.
+        */
+       public int getContentLength() {
+               return contentLength == 0 ? req.getRawContentLength() : 
contentLength;
+       }
+
+       /**
+        * ServletInputStream wrapper around a normal input stream.
+        */
+       private static class ServletInputStream2 extends ServletInputStream {
+
+               private final InputStream is;
+
+               private ServletInputStream2(InputStream is) {
+                       this.is = is;
+               }
+
+               private ServletInputStream2(byte[] b) {
+                       this(new ByteArrayInputStream(b));
+               }
+
+               @Override /* InputStream */
+               public final int read() throws IOException {
+                       return is.read();
+               }
+
+               @Override /* InputStream */
+               public final void close() throws IOException {
+                       is.close();
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/RequestFormData.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest/src/main/java/org/apache/juneau/rest/RequestFormData.java 
b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestFormData.java
new file mode 100644
index 0000000..12d2157
--- /dev/null
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RequestFormData.java
@@ -0,0 +1,271 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                
                                              *
+// *                                                                           
                                              *
+// *  http://www.apache.org/licenses/LICENSE-2.0                               
                                              *
+// *                                                                           
                                              *
+// * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the 
License.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau.rest;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Represents the parsed form data parameters in an HTTP request.
+ */
+@SuppressWarnings("unchecked")
+public class RequestFormData extends LinkedHashMap<String,String[]> {
+       private static final long serialVersionUID = 1L;
+
+       private UrlEncodingParser parser;
+       private BeanSession beanSession;
+
+       RequestFormData setParser(UrlEncodingParser parser) {
+               this.parser = parser;
+               return this;
+       }
+
+       RequestFormData setBeanSession(BeanSession beanSession) {
+               this.beanSession = beanSession;
+               return this;
+       }
+
+       /**
+        * Sets a request form data parameter value.
+        *
+        * @param name The parameter name.
+        * @param value The parameter value.
+        */
+       public void put(String name, Object value) {
+               super.put(name, new String[]{StringUtils.toString(value)});
+       }
+
+       /**
+        * Returns a form data parameter value.
+        * <p>
+        * Parameter lookup is case-insensitive (consistent with WAS, but 
differs from Tomcat).
+        * <p>
+        * <h5 class='section'>Notes:</h5>
+        * <ul>
+        *      <li>Calling this method on URL-Encoded FORM posts causes the 
body content to be loaded and parsed by the underlying servlet API.
+        *      <li>This method returns the raw unparsed value, and differs 
from calling <code>getFormDataParameter(name, String.<jk>class</js>)</code>
+        *              which will convert the value from UON notation:
+        *              <ul>
+        *                      <li><js>"null"</js> =&gt; <jk>null</jk>
+        *                      <li><js>"'null'"</js> =&gt; <js>"null"</js>
+        *                      <li><js>"'foo bar'"</js> =&gt; <js>"foo 
bar"</js>
+        *                      <li><js>"foo~~bar"</js> =&gt; <js>"foo~bar"</js>
+        *              </ul>
+        * </ul>
+        *
+        * @param name The form data parameter name.
+        * @return The parameter value, or <jk>null</jk> if parameter does not 
exist.
+        */
+       public String getFirst(String name) {
+               String[] v = get(name);
+               if (v == null || v.length == 0)
+                       return null;
+               if (v.length == 1 && v[0] != null && v[0].isEmpty()) {
+                       // Fix for behavior difference between Tomcat and WAS.
+                       // getParameter("foo") on "&foo" in Tomcat returns "".
+                       // getParameter("foo") on "&foo" in WAS returns null.
+                       if (containsKey(name))
+                               return null;
+               }
+               return v[0];
+       }
+
+       /**
+        * Same as {@link #getFirst(String)} except returns a default value if 
<jk>null</jk> or empty.
+        *
+        * @param name The form data parameter name.
+        * @param def The default value.
+        * @return The parameter value, or the default value if <jk>null</jk> 
or empty.
+        */
+       public String getFirst(String name, String def) {
+               String val = getFirst(name);
+               if (val == null || val.isEmpty())
+                       return def;
+               return val;
+       }
+
+       /**
+        * Returns the specified form data parameter value converted to a POJO 
using the
+        *      {@link UrlEncodingParser} registered with this servlet.
+        * <p>
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode'>
+        *      <jc>// Parse into an integer.</jc>
+        *      <jk>int</jk> myparam = 
req.getFormDataParameter(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>);
+        *
+        *      <jc>// Parse into an int array.</jc>
+        *      <jk>int</jk>[] myparam = 
req.getFormDataParameter(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>);
+
+        *      <jc>// Parse into a bean.</jc>
+        *      MyBean myparam = req.getFormDataParameter(<js>"myparam"</js>, 
MyBean.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a linked-list of objects.</jc>
+        *      List myparam = req.getFormDataParameter(<js>"myparam"</js>, 
LinkedList.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map of object keys/values.</jc>
+        *      Map myparam = req.getFormDataParameter(<js>"myparam"</js>, 
TreeMap.<jk>class</jk>);
+        * </p>
+        * <p>
+        * <h5 class='section'>Notes:</h5>
+        * <ul>
+        *      <li>Calling this method on URL-Encoded FORM posts causes the 
body content to be loaded and parsed by the underlying servlet API.
+        * </ul>
+        *
+        * @param name The parameter name.
+        * @param type The class type to convert the parameter value to.
+        * @param <T> The class type to convert the parameter value to.
+        * @return The parameter value converted to the specified class type.
+        * @throws ParseException
+        */
+       public <T> T get(String name, Class<T> type) throws ParseException {
+               return parse(name, beanSession.getClassMeta(type));
+       }
+
+       /**
+        * Same as {@link #get(String, Class)} except returns a default value 
if not specified.
+        *
+        * @param name The parameter name.
+        * @param def The default value if the parameter was not specified or 
is <jk>null</jk>.
+        * @param type The class type to convert the parameter value to.
+        * @param <T> The class type to convert the parameter value to.
+        * @return The parameter value converted to the specified class type.
+        * @throws ParseException
+        */
+       public <T> T get(String name, T def, Class<T> type) throws 
ParseException {
+               return parse(name, def, beanSession.getClassMeta(type));
+       }
+
+       /**
+        * Same as {@link #get(String, Class)} except for use on multi-part 
parameters
+        *      (e.g. <js>"key=1&amp;key=2&amp;key=3"</js> instead of 
<js>"key=(1,2,3)"</js>)
+        * <p>
+        * This method must only be called when parsing into classes of type 
Collection or array.
+        *
+        * @param name The parameter name.
+        * @param type The class type to convert the parameter value to.
+        * @return The parameter value converted to the specified class type.
+        * @throws ParseException
+        */
+       public <T> T getAll(String name, Class<T> type) throws ParseException {
+               return parseAll(name, beanSession.getClassMeta(type));
+       }
+
+       /**
+        * Returns the specified form data parameter value converted to a POJO 
using the
+        *      {@link UrlEncodingParser} registered with this servlet.
+        * <p>
+        * <h5 class='section'>Notes:</h5>
+        * <ul>
+        *      <li>Calling this method on URL-Encoded FORM posts causes the 
body content to be loaded and parsed by the underlying servlet API.
+        *      <li>Use this method if you want to parse into a parameterized 
<code>Map</code>/<code>Collection</code> object.
+        * </ul>
+        * <p>
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode'>
+        *      <jc>// Parse into a linked-list of strings.</jc>
+        *      List&lt;String&gt; myparam = 
req.getFormDataParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>, 
String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a linked-list of linked-lists of strings.</jc>
+        *      List&lt;List&lt;String&gt;&gt; myparam = 
req.getFormDataParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>, 
LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map of string keys/values.</jc>
+        *      Map&lt;String,String&gt; myparam = 
req.getFormDataParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, 
String.<jk>class</jk>, String.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map containing string keys and values of 
lists containing beans.</jc>
+        *      Map&lt;String,List&lt;MyBean&gt;&gt; myparam = 
req.getFormDataParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, 
String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);
+        * </p>
+        *
+        * @param name The parameter name.
+        * @param type The type of object to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType}, {@link GenericArrayType}
+        * @param args The type arguments of the class if it's a collection or 
map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType}, {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @return The parameter value converted to the specified class type.
+        * @throws ParseException
+        */
+       public <T> T get(String name, Type type, Type...args) throws 
ParseException {
+               return (T)parse(name, beanSession.getClassMeta(type, args));
+       }
+
+       /**
+        * Same as {@link #get(String, Type, Type...)} except for use on 
multi-part parameters
+        *      (e.g. <js>"key=1&amp;key=2&amp;key=3"</js> instead of 
<js>"key=(1,2,3)"</js>)
+        * <p>
+        * This method must only be called when parsing into classes of type 
Collection or array.
+        *
+        * @param name The parameter name.
+        * @param type The type of object to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType}, {@link GenericArrayType}
+        * @param args The type arguments of the class if it's a collection or 
map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType}, {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @return The parameter value converted to the specified class type.
+        * @throws ParseException
+        */
+       public <T> T getAll(String name, Type type, Type...args) throws 
ParseException {
+               return (T)parseAll(name, beanSession.getClassMeta(type, args));
+       }
+
+       /* Workhorse method */
+       <T> T parse(String name, T def, ClassMeta<T> cm) throws ParseException {
+               String val = getFirst(name);
+               if (val == null)
+                       return def;
+               return parseValue(val, cm);
+       }
+
+       /* Workhorse method */
+       <T> T parse(String name, ClassMeta<T> cm) throws ParseException {
+               String val = getFirst(name);
+               if (cm.isPrimitive() && (val == null || val.isEmpty()))
+                       return cm.getPrimitiveDefault();
+               return parseValue(val, cm);
+       }
+
+       /* Workhorse method */
+       @SuppressWarnings("rawtypes")
+       <T> T parseAll(String name, ClassMeta<T> cm) throws ParseException {
+               String[] p = get(name);
+               if (p == null)
+                       return null;
+               if (cm.isArray()) {
+                       List c = new ArrayList();
+                       for (int i = 0; i < p.length; i++)
+                               c.add(parseValue(p[i], cm.getElementType()));
+                       return (T)ArrayUtils.toArray(c, 
cm.getElementType().getInnerClass());
+               } else if (cm.isCollection()) {
+                       try {
+                               Collection c = 
(Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new ObjectList());
+                               for (int i = 0; i < p.length; i++)
+                                       c.add(parseValue(p[i], 
cm.getElementType()));
+                               return (T)c;
+                       } catch (ParseException e) {
+                               throw e;
+                       } catch (Exception e) {
+                               // Typically an instantiation exception.
+                               throw new ParseException(e);
+                       }
+               }
+               throw new ParseException("Invalid call to getParameters(String, 
ClassMeta).  Class type must be a Collection or array.");
+       }
+
+       private <T> T parseValue(String val, ClassMeta<T> c) throws 
ParseException {
+               return parser.parsePart(val, c);
+       }
+}

Reply via email to