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><object></xt>...<xt></object></xt></code><br><code><xt><x</xt> <xa>type</xa>=<xs>'object'</xs><xt>></xt>...<xt></x></xt></code></td> + * <td>{@link ObjectMap}</td> + * </tr> + * <tr> + * <td>array</td> + * <td><js>"[...]"</js></td> + * <td><code><xt><array></xt>...<xt></array></xt></code><br><code><xt><x</xt> <xa>type</xa>=<xs>'array'</xs><xt>></xt>...<xt></x></xt></code></td> + * <td>{@link ObjectList}</td> + * </tr> + * <tr> + * <td>string</td> + * <td><js>"'...'"</js></td> + * <td><code><xt><string></xt>...<xt></string></xt></code><br><code><xt><x</xt> <xa>type</xa>=<xs>'string'</xs><xt>></xt>...<xt></x></xt></code></td> + * <td>{@link String}</td> + * </tr> + * <tr> + * <td>number</td> + * <td><code>123</code></td> + * <td><code><xt><number></xt>123<xt></number></xt></code><br><code><xt><x</xt> <xa>type</xa>=<xs>'number'</xs><xt>></xt>...<xt></x></xt></code></td> + * <td>{@link Number}</td> + * </tr> + * <tr> + * <td>boolean</td> + * <td><jk>true</jk></td> + * <td><code><xt><boolean></xt>true<xt></boolean></xt></code><br><code><xt><x</xt> <xa>type</xa>=<xs>'boolean'</xs><xt>></xt>...<xt></x></xt></code></td> + * <td>{@link Boolean}</td> + * </tr> + * <tr> + * <td>null</td> + * <td><jk>null</jk> or blank</td> + * <td><code><xt><null/></xt></code> or blank<br><code><xt><x</xt> <xa>type</xa>=<xs>'null'</xs><xt>/></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<String> body = req.getBody(LinkedList.<jk>class</jk>, String.<jk>class</jk>); + * + * <jc>// Parse into a linked-list of linked-lists of strings.</jc> + * List<List<String>> 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<String,String> 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<String,List<MyBean>> 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> => <jk>null</jk> + * <li><js>"'null'"</js> => <js>"null"</js> + * <li><js>"'foo bar'"</js> => <js>"foo bar"</js> + * <li><js>"foo~~bar"</js> => <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&key=2&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<String> 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<List<String>> 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<String,String> 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<String,List<MyBean>> 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&key=2&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); + } +}
