Repository: incubator-juneau Updated Branches: refs/heads/master fa4736b6d -> 321f6bdee
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java index c35e0bd..dfced43 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java @@ -31,12 +31,10 @@ import javax.servlet.http.*; import org.apache.juneau.*; import org.apache.juneau.dto.swagger.*; import org.apache.juneau.encoders.*; -import org.apache.juneau.encoders.Encoder; import org.apache.juneau.http.*; import org.apache.juneau.ini.*; import org.apache.juneau.internal.*; import org.apache.juneau.parser.*; -import org.apache.juneau.parser.ParseException; import org.apache.juneau.serializer.*; import org.apache.juneau.svl.*; import org.apache.juneau.uon.*; @@ -70,26 +68,22 @@ public final class RestRequest extends HttpServletRequestWrapper { private final String method; private String pathRemainder; - private byte[] body; + private RequestBody body; private Method javaMethod; private ObjectMap properties; private SerializerGroup serializerGroup; private ParserGroup parserGroup; - private EncoderGroup encoders; - private Encoder encoder; - private int contentLength; private final boolean debug; private UrlEncodingParser urlEncodingParser; // The parser used to parse URL attributes and parameters (beanContext also used to parse headers) private BeanSession beanSession; private VarResolverSession varSession; - private final Map<String,String[]> queryParams; - private Map<String,String> formData; - private final Map<String,String> defaultServletHeaders; - private Map<String,String> defaultMethodHeaders, overriddenHeaders, overriddenQueryParams, overriddenFormDataParams, pathParameters; + private final RequestQuery queryParams; + private RequestFormData formData; + private RequestPathParams pathParams; private boolean isPost; private String servletURI, relativeServletURI; private String charset, defaultCharset; - private ObjectMap headers; + private RequestHeaders headers; private ConfigFile cf; private Swagger swagger, fileSwagger; private String pageTitle, pageText, pageLinks; @@ -106,33 +100,45 @@ public final class RestRequest extends HttpServletRequestWrapper { // If this is a POST, we want to parse the query parameters ourselves to prevent // the servlet code from processing the HTTP body as URL-Encoded parameters. + queryParams = new RequestQuery(); if (isPost) - queryParams = context.getUrlEncodingParser().parseIntoSimpleMap(getQueryString()); - else { - queryParams = req.getParameterMap(); - } + context.getUrlEncodingParser().parseIntoSimpleMap(getQueryString(), queryParams); + else + queryParams.putAll(req.getParameterMap()); + // Get the HTTP method. // Can be overridden through a "method" GET attribute. String _method = super.getMethod(); - String m = getQueryParameter("method"); + String m = getQuery("method"); if (context.allowMethodParam(m)) _method = m; method = _method; + headers = new RequestHeaders(); + for (Enumeration<String> e = getHeaderNames(); e.hasMoreElements();) { + String name = e.nextElement(); + headers.put(name, super.getHeaders(name)); + } + + body = new RequestBody(this); + if (context.isAllowBodyParam()) { - String b = getQueryParameter("body"); + String b = getQuery("body"); if (b != null) { - setHeader("Content-Type", UonSerializer.DEFAULT.getResponseContentType()); - this.body = b.getBytes(IOUtils.UTF8); + headers.put("Content-Type", UonSerializer.DEFAULT.getResponseContentType()); + body.load(b.getBytes(IOUtils.UTF8)); } } - defaultServletHeaders = context.getDefaultRequestHeaders(); + if (context.isAllowHeaderParams()) + headers.setQueryParams(queryParams); - debug = "true".equals(getQueryParameter("debug", "false")) || "true".equals(getHeader("Debug", "false")); + debug = "true".equals(getQuery("debug", "false")) || "true".equals(getHeader("Debug", "false")); + + this.pathParams = new RequestPathParams(); } catch (RestException e) { throw e; @@ -149,13 +155,28 @@ public final class RestRequest extends HttpServletRequestWrapper { this.javaMethod = javaMethod; this.pathRemainder = pathRemainder; this.properties = properties; - this.defaultMethodHeaders = mDefaultRequestHeaders; - this.serializerGroup = mSerializers; - this.parserGroup = mParsers; this.urlEncodingParser = mUrlEncodingParser; this.beanSession = urlEncodingParser.getBeanContext().createSession(); + this.pathParams + .setParser(urlEncodingParser) + .setBeanSession(beanSession); + this.queryParams + .setParser(urlEncodingParser) + .setBeanSession(beanSession); + this.headers + .addDefault(mDefaultRequestHeaders) + .addDefault(context.getDefaultRequestHeaders()) + .setParser(urlEncodingParser) + .setBeanSession(beanSession); + this.body + .setEncoders(encoders) + .setParsers(mParsers) + .setHeaders(headers) + .setBeanSession(beanSession) + .setUrlEncodingParser(mUrlEncodingParser); + this.serializerGroup = mSerializers; + this.parserGroup = mParsers; this.defaultCharset = defaultCharset; - this.encoders = encoders; this.pageTitle = pageTitle; this.pageText = pageText; this.pageLinks = pageLinks; @@ -209,188 +230,49 @@ public final class RestRequest extends HttpServletRequestWrapper { //-------------------------------------------------------------------------------- /** - * Sets a request header value. + * Returns the headers on this request. + * + * @return The headers on this request. Never <jk>null</jk>. + */ + public RequestHeaders getHeaders() { + return headers; + } + + /** + * Convenience method for calling <code>getHeaders().put(name, value);</code> * * @param name The header name. * @param value The header value. */ public void setHeader(String name, Object value) { - if (overriddenHeaders == null) - overriddenHeaders = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER); - overriddenHeaders.put(name, StringUtils.toString(value)); + getHeaders().put(name, value); } - /** - * Returns the specified header value, or <jk>null</jk> if the header doesn't exist. - * <p> - * If {@code allowHeaderParams} init parameter is <jk>true</jk>, then first looks for {@code &HeaderName=x} in the URL query string. + * Convenience method for calling <code>getHeaders().getFirst(name);</code> */ @Override /* ServletRequest */ public String getHeader(String name) { - return getHeader(name, (String)null); + return getHeaders().getFirst(name); } /** - * Returns the specified header value, or a default value if the header doesn't exist. - * <p> - * If {@code allowHeaderParams} init parameter is <jk>true</jk>, then first looks for {@code &HeaderName=x} in the URL query string. + * Convenience method for calling <code>getHeaders().getFirst(name, def);</code> * * @param name The HTTP header name. * @param def The default value to return if the header value isn't found. * @return The header value, or the default value if the header isn't present. */ public String getHeader(String name, String def) { - String h = getOverriddenHeader(name); - if (h != null) - return h; - h = super.getHeader(name); - if (h != null && ! h.isEmpty()) - return h; - if (defaultMethodHeaders != null) { - h = defaultMethodHeaders.get(name); - if (h != null) - return h; - } - h = defaultServletHeaders.get(name); - if (h != null) - return h; - return def; - } - - /** - * Returns the specified header value converted to a POJO. - * <p> - * The type can be any POJO type convertable from a <code>String</code> - * (See <a class="doclink" href="package-summary.html#PojosConvertableFromString">POJOs Convertable From Strings</a>). - * <p> - * <h5 class='section'>Examples:</h5> - * <p class='bcode'> - * <jc>// Parse into an integer.</jc> - * <jk>int</jk> myheader = req.getHeader(<js>"My-Header"</js>, <jk>int</jk>.<jk>class</jk>); - * - * <jc>// Parse a UUID.</jc> - * UUID myheader = req.getHeader(<js>"My-Header"</js>, UUID.<jk>class</jk>); - * </p> - * - * @param name The HTTP header name. - * @param type The class type to convert the header value to. - * @param <T> The class type to convert the header value to. - * @return The parameter value converted to the specified class type. - */ - public <T> T getHeader(String name, Class<T> type) { - String h = getHeader(name); - return beanSession.convertToType(h, type); - } - - /** - * Same as {@link #getHeader(String, Class)} but returns a default value if not found. - * - * @param name The HTTP header name. - * @param def The default value if the header was not specified or is <jk>null</jk>. - * @param type The class type to convert the header value to. - * @param <T> The class type to convert the header value to. - * @return The parameter value converted to the specified class type. - */ - public <T> T getHeader(String name, T def, Class<T> type) { - String h = getHeader(name); - if (h == null) - return def; - return beanSession.convertToType(h, type); - } - - /** - * Returns the specified header value converted to a POJO. - * <p> - * The type can be any POJO type convertable from a <code>String</code> - * (See <a class="doclink" href="package-summary.html#PojosConvertableFromString">POJOs Convertable From Strings</a>). - * <p> - * <h5 class='section'>Examples:</h5> - * <p class='bcode'> - * <jc>// Parse into a linked-list of strings.</jc> - * List<String> myheader = req.getHeader(<js>"My-Header"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); - * </p> - * - * @param name The HTTP header 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. - * @param <T> The class type to convert the header value to. - * @return The parameter value converted to the specified class type. - * @throws ParseException If the header could not be converted to the specified type. - */ - public <T> T getHeader(String name, Type type, Type...args) throws ParseException { - String h = getHeader(name); - return urlEncodingParser.parsePart(h, type, args); - } - - /** - * Returns all the request headers as an {@link ObjectMap}. - * <p> - * Altering entries in this map does not alter headers in the underlying request. - * - * @return The request headers. Never <jk>null</jk>. - */ - public ObjectMap getHeaders() { - if (headers == null) { - headers = new ObjectMap(); - for (Enumeration<String> e = getHeaderNames(); e.hasMoreElements();) { - String key = e.nextElement(); - headers.put(key, getHeader(key)); - } - } - return headers; + return getHeaders().getFirst(name, def); } @Override /* ServletRequest */ public Enumeration<String> getHeaders(String name) { - String h = getOverriddenHeader(name); - if (h != null) - return enumeration(singleton(h)); - return super.getHeaders(name); - } - - /** - * Returns the <code>Accept</code> header on the request. - * - * @return The parsed <code>Accept</code> header on the request, or <jk>null</jk> if not found. - */ - public Accept getAcceptHeader() { - return getHeader("Accept", Accept.class); - } - - /** - * Returns the <code>Content-Type</code> header on the request. - * - * @return The parsed <code>Content-Type</code> header on the request, or <jk>null</jk> if not found. - */ - public ContentType getContentTypeHeader() { - return getHeader("Content-Type", ContentType.class); - } - - /** - * Returns the <code>Accept-Encoding</code> header on the request. - * - * @return The parsed <code>Accept-Encoding</code> header on the request, or <jk>null</jk> if not found. - */ - public AcceptEncoding getAcceptEncodingHeader() { - return getHeader("Accept-Encoding", AcceptEncoding.class); - } - - /** - * Returns the <code>Time-Zone</code> header value on the request if there is one. - * <p> - * Example: <js>"GMT"</js>. - * - * @return The <code>Time-Zone</code> header value on the request, or <jk>null</jk> if not present. - */ - public TimeZone getTimeZone() { - String tz = getHeader("Time-Zone"); - if (tz != null) - return TimeZone.getTimeZone(tz); - return null; + String[] v = headers.get(name); + if (v == null || v.length == 0) + return Collections.enumeration(Collections.EMPTY_LIST); + return Collections.enumeration(Arrays.asList(v)); } /** @@ -435,7 +317,7 @@ public final class RestRequest extends HttpServletRequestWrapper { @Override /* ServletRequest */ public Locale getLocale() { - String h = getOverriddenHeader("Accept-Language"); + String h = headers.getFirst("Accept-Language"); if (h != null) { MediaTypeRange[] mr = MediaTypeRange.parse(h); if (mr.length > 0) @@ -446,7 +328,7 @@ public final class RestRequest extends HttpServletRequestWrapper { @Override /* ServletRequest */ public Enumeration<Locale> getLocales() { - String h = getOverriddenHeader("Accept-Language"); + String h = headers.getFirst("Accept-Language"); if (h != null) { MediaTypeRange[] mr = MediaTypeRange.parse(h); if (mr.length > 0) { @@ -465,672 +347,142 @@ public final class RestRequest extends HttpServletRequestWrapper { //-------------------------------------------------------------------------------- /** - * Sets a request query parameter value. - * - * @param name The parameter name. - * @param value The parameter value. - */ - public void setQueryParameter(String name, Object value) { - if (overriddenQueryParams == null) - overriddenQueryParams = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER); - overriddenQueryParams.put(name, value == null ? null : value.toString()); - } - - /** - * Returns a query parameter value. - * <p> - * Same as {@link #getParameter(String)} except only looks in the URL string, not parameters from URL-Encoded FORM posts. - * <p> - * This method can be used to retrieve a parameter without triggering the underlying servlet API to load and parse the request body. - * - * @param name The URL parameter name. - * @return The parameter value, or <jk>null</jk> if parameter not specified or has no value (e.g. <js>"&foo"</js>. - */ - public String getQueryParameter(String name) { - String s = null; - if (overriddenQueryParams != null) - s = overriddenQueryParams.get(name); - if (s != null) - return s; - String[] v = queryParams.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 (queryParams.containsKey(name)) - return null; - } - return v[0]; - } - - /** - * Same as {@link #getQueryParameter(String)} but returns the specified default value if the query parameter was not specified. - * - * @param name The URL parameter name. - * @param def The default value. - * @return The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&foo"</js>. - */ - public String getQueryParameter(String name, String def) { - String s = getQueryParameter(name); - return s == null ? def : s; - } - - /** - * Returns the specified query parameter value converted to a POJO. - * <p> - * This method can be used to retrieve a parameter without triggering the underlying servlet API to load and parse the request body. - * <p> - * <h5 class='section'>Examples:</h5> - * <p class='bcode'> - * <jc>// Parse into an integer.</jc> - * <jk>int</jk> myparam = req.getQueryParameter(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>); - * - * <jc>// Parse into an int array.</jc> - * <jk>int</jk>[] myparam = req.getQueryParameter(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>); - - * <jc>// Parse into a bean.</jc> - * MyBean myparam = req.getQueryParameter(<js>"myparam"</js>, MyBean.<jk>class</jk>); - * - * <jc>// Parse into a linked-list of objects.</jc> - * List myparam = req.getQueryParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>); - * - * <jc>// Parse into a map of object keys/values.</jc> - * Map myparam = req.getQueryParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>); - * </p> - * - * @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 getQueryParameter(String name, Class<T> type) throws ParseException { - return getQueryParameter(name, beanSession.getClassMeta(type)); - } - - /** - * Same as {@link #getQueryParameter(String, Class)} except returns a default value if not found. - * - * @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 getQueryParameter(String name, T def, Class<T> type) throws ParseException { - return getQueryParameter(name, def, beanSession.getClassMeta(type)); - } - - /** - * Returns the specified query parameter value converted to a POJO. - * <p> - * This method can be used to retrieve a parameter without triggering the underlying servlet API to load and parse the request body. + * Equivalent to {@link #getParameterMap()}, but only looks for query parameters in the URL, not form posts. * <p> - * Use this method if you want to parse into a parameterized <code>Map</code>/<code>Collection</code> object. + * This method can be used to retrieve query parameters without triggering the underlying servlet API to load and parse the request body. * <p> - * <h5 class='section'>Examples:</h5> - * <p class='bcode'> - * <jc>// Parse into a linked-list of strings.</jc> - * Listt<String> myparam = req.getQueryParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); - * - * <jc>// Parse into a linked-list of linked-lists of strings.</jc> - * Listt<List<String>> myparam = req.getQueryParameter(<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.getQueryParameter(<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.getQueryParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); - * </p> + * This object is modifiable. * - * @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. - * @param <T> The class type to convert the parameter value to. - * @return The parameter value converted to the specified class type. - * @throws ParseException + * @return The query parameters as a modifiable map. */ - public <T> T getQueryParameter(String name, Type type, Type...args) throws ParseException { - return (T)getQueryParameter(name, beanSession.getClassMeta(type, args)); + public RequestQuery getQuery() { + return queryParams; } /** - * Same as {@link #getQueryParameter(String, Class)} except returns a default value if not found. + * Convenience method for calling <code>getQueryParams().put(name, value);</code>. * * @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. - * @param def The default value if the parameter was not specified or is <jk>null</jk>. - * @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 getQueryParameter(String name, Object def, Type type, Type...args) throws ParseException { - return (T)getQueryParameter(name, def, getBeanSession().getClassMeta(type, args)); - } - - /** - * Same as {@link #getQueryParameter(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 query parameter name. - * @param c The class type to convert the parameter value to. - * @param <T> The class type to convert the parameter value to. - * @return The query parameter value converted to the specified class type. - * @throws ParseException - */ - public <T> T getQueryParameters(String name, Class<T> c) throws ParseException { - return getQueryParameters(name, beanSession.getClassMeta(c)); - } - - /** - * Same as {@link #getQueryParameter(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 query 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. - * @param <T> The class type to convert the parameter value to. - * @return The query parameter value converted to the specified class type. - * @throws ParseException + * @param value The parameter value. */ - public <T> T getQueryParameters(String name, Type type, Type...args) throws ParseException { - return (T)getQueryParameters(name, getBeanSession().getClassMeta(type, args)); + public void setQuery(String name, Object value) { + queryParams.put(name, value); } /** - * Returns the list of all query parameters with the specified name. - * <p> - * Same as {@link #getParameterValues(String)} except only looks in the URL string, not parameters from URL-Encoded FORM posts. - * <p> - * This method can be used to retrieve parameters without triggering the underlying servlet API to load and parse the request body. + * Convenience method for calling <code>getQueryParams().getFirst(name);</code> * - * @param name - * @return the list of query parameters, or <jk>null</jk> if the parameter does not exist. + * @param name The URL parameter name. + * @return The parameter value, or <jk>null</jk> if parameter not specified or has no value (e.g. <js>"&foo"</js>. */ - public String[] getQueryParameters(String name) { - return queryParams.get(name); + public String getQuery(String name) { + return getQuery().getFirst(name); } /** - * Returns <jk>true</jk> if the query parameters on this request contains the specified entry. - * <p> - * Note that this returns <jk>true</jk> even if the value is set to null (e.g. <js>"?key"</js>). - * <p> - * This method can be used to check the existence of a parameter without triggering the underlying - * servlet API to load and parse the request body. + * Convenience method for calling <code>getQueryParams().getFirst(name, def);</code> * * @param name The URL parameter name. - * @return <jk>true</jk> if the URL parameters on this request contains the specified entry. + * @param def The default value. + * @return The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&foo"</js>. */ - public boolean hasQueryParameter(String name) { - return queryParams.containsKey(name); + public String getQuery(String name, String def) { + return getQuery().getFirst(name, def); } - /** - * Returns <jk>true</jk> if the request contains any of the specified query parameters. - * - * @param params The list of parameters to check for. - * @return <jk>true</jk> if the request contains any of the specified query parameters. - */ - public boolean hasAnyQueryParameters(String...params) { - for (String p : params) - if (hasQueryParameter(p)) - return true; - return false; - } - /** - * Equivalent to {@link #getParameterMap()}, but only looks for query parameters in the URL, not form posts. - * <p> - * This method can be used to retrieve query parameters without triggering the underlying servlet API to load and parse the request body. - * <p> - * This object is modifiable. - * - * @return The query parameters as a modifiable map. - */ - public Map<String,String[]> getQueryParameterMap() { - return queryParams; - } + //-------------------------------------------------------------------------------- + // Form data parameters + //-------------------------------------------------------------------------------- /** - * Equivalent to {@link #getParameterNames()}, but only looks for query parameters in the URL, not form posts. - * <p> - * This method can be used to retrieve query parameters without triggering the underlying servlet API to load and parse the request body. - * <p> - * This object is modifiable. + * Retrieves the URL-encoded form data from the request if the body has already been cached locally. * - * @return An iterator of query parameter names. + * @return The URL-encoded form data from the request. */ - public Iterator<String> getQueryParameterNames() { - return queryParams.keySet().iterator(); - } - - /* Workhorse method */ - <T> T getQueryParameter(String name, T def, ClassMeta<T> cm) throws ParseException { - String val = getQueryParameter(name); - if (val == null) - return def; - return parseParameter(val, cm); - } - - /* Workhorse method */ - <T> T getQueryParameter(String name, ClassMeta<T> cm) throws ParseException { - String val = getQueryParameter(name); - if (cm.isPrimitive() && (val == null || val.isEmpty())) - return cm.getPrimitiveDefault(); - return parseParameter(val, cm); - } - - /* Workhorse method */ - @SuppressWarnings("rawtypes") - <T> T getQueryParameters(String name, ClassMeta<T> cm) throws ParseException { - String[] p = getQueryParameters(name); - if (p == null) - return null; - if (cm.isArray()) { - List c = new ArrayList(); - for (int i = 0; i < p.length; i++) - c.add(parseParameter(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(parseParameter(p[i], cm.getElementType())); - return (T)c; - } catch (ParseException e) { - throw e; - } catch (Exception e) { - // Typically an instantiation exception. - throw new ParseException(e); + public RequestFormData getFormData() { + try { + if (formData == null) { + formData = new RequestFormData(); + formData.setParser(urlEncodingParser).setBeanSession(beanSession); + if (! body.isLoaded()) { + formData.putAll(getParameterMap()); + } else { + Map<String,String> m = urlEncodingParser.parse(body.getReader(), Map.class, String.class, String.class); + for (Map.Entry<String,String> e : m.entrySet()) { + formData.put(e.getKey(), e.getValue()); + } + } } + return formData; + } catch (Exception e) { + throw new RestException(SC_INTERNAL_SERVER_ERROR, e); } - throw new ParseException("Invalid call to getQueryParameters(String, ClassMeta). Class type must be a Collection or array."); } - - //-------------------------------------------------------------------------------- - // Form data parameters - //-------------------------------------------------------------------------------- - /** - * Sets a request form data parameter value. + * Convenience method for calling <code>getFormData().put(name, value);</code>. * * @param name The parameter name. * @param value The parameter value. */ - public void setFormDataParameter(String name, Object value) { - if (overriddenFormDataParams == null) - overriddenFormDataParams = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER); - overriddenFormDataParams.put(name, value == null ? null : value.toString()); + public void setFormData(String name, Object value) { + getFormData().put(name, 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> + * Convenience method for calling <code>getFormData().getFirst(name);</code>. * * @param name The form data parameter name. * @return The parameter value, or <jk>null</jk> if parameter does not exist. */ - public String getFormDataParameter(String name) { - String s = null; - if (overriddenFormDataParams != null) - s = overriddenFormDataParams.get(name); - if (s != null) - return s; - - return getFormParameterInner(name); + public String getFormData(String name) { + return getFormData().getFirst(name); } /** - * Same as {@link #getFormDataParameter(String)} except returns a default value if <jk>null</jk> or empty. + * Convenience method for calling <code>getFormData().getFirst(name, def);</code>. * * @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 getFormDataParameter(String name, String def) { - String val = getFormParameterInner(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 getFormDataParameter(String name, Class<T> type) throws ParseException { - return getFormDataParameter(name, beanSession.getClassMeta(type)); + public String getFormData(String name, String def) { + return getFormData().getFirst(name, def); } - /** - * Same as {@link #getFormDataParameter(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 getFormDataParameter(String name, T def, Class<T> type) throws ParseException { - return getFormDataParameter(name, def, beanSession.getClassMeta(type)); - } - /** - * Same as {@link #getFormDataParameter(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 getFormDataParameters(String name, Class<T> type) throws ParseException { - return getFormDataParameters(name, beanSession.getClassMeta(type)); - } + //-------------------------------------------------------------------------------- + // Path parameters + //-------------------------------------------------------------------------------- /** - * 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> + * Retrieves the URL-encoded form data from the request if the body has already been cached locally. * - * @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 + * @return The URL-encoded form data from the request. */ - public <T> T getFormDataParameter(String name, Type type, Type...args) throws ParseException { - return (T)getFormDataParameter(name, beanSession.getClassMeta(type, args)); + public RequestPathParams getPathParams() { + return pathParams; } /** - * Same as {@link #getFormDataParameter(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 getFormDataParameters(String name, Type type, Type...args) throws ParseException { - return (T)getFormDataParameters(name, beanSession.getClassMeta(type, args)); - } - - /** - * Returns <jk>true</jk> if the form data parameters on this request contains the specified entry. - * <p> - * Note that this returns <jk>true</jk> even if the value is set to null (e.g. <js>"?key"</js>). - * - * @param name The URL parameter name. - * @return <jk>true</jk> if the URL parameters on this request contains the specified entry. - */ - public boolean hasFormDataParameter(String name) { - return getParameterMap().containsKey(name); - } - - /** - * Retrieves the URL-encoded from data from the request if the body has already been cached locally. - */ - private Map<String,String> getFormData() { - try { - if (formData == null) - formData = urlEncodingParser.parse(body, Map.class, String.class, String.class); - return formData; - } catch (ParseException e) { - throw new RestException(SC_INTERNAL_SERVER_ERROR, e); - } - } - - /* Workhorse method */ - <T> T getFormDataParameter(String name, T def, ClassMeta<T> cm) throws ParseException { - String val = getFormParameterInner(name); - if (val == null) - return def; - return parseParameter(val, cm); - } - - /* Workhorse method */ - <T> T getFormDataParameter(String name, ClassMeta<T> cm) throws ParseException { - String val = getFormParameterInner(name); - if (cm.isPrimitive() && (val == null || val.isEmpty())) - return cm.getPrimitiveDefault(); - return parseParameter(val, cm); - } - - private String getFormParameterInner(String name) { - return (body == null ? super.getParameter(name) : getFormData().get(name)); - } - - /* Workhorse method */ - @SuppressWarnings("rawtypes") - <T> T getFormDataParameters(String name, ClassMeta<T> cm) throws ParseException { - String[] p = getParameterValues(name); - if (p == null) - return null; - if (cm.isArray()) { - List c = new ArrayList(); - for (int i = 0; i < p.length; i++) - c.add(parseParameter(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(parseParameter(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."); - } - - - //-------------------------------------------------------------------------------- - // Path parameters - //-------------------------------------------------------------------------------- - - /** - * Sets a path parameter value. - * <p> - * A path parameter is a variable in the path pattern such as <js>"/{foo}"</js> + * Convenience method for calling <code>getPathParams().put(name, value);</code>. * * @param name The parameter name. * @param value The parameter value. */ public void setPathParameter(String name, String value) { - if (pathParameters == null) - pathParameters = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER); - pathParameters.put(name, value == null ? null : value.toString()); + pathParams.put(name, StringUtils.toString(value)); } /** - * Returns a path parameter value. - * <p> - * A path parameter is a variable in the path pattern such as <js>"/{foo}"</js> + * Convenience method for calling <code>getPathParams().get(name);</code>. * * @param name The parameter name. - * @return The paramter value, or <jk>null</jk> if path parameter not specified. + * @return The parameter value, or <jk>null</jk> if path parameter not specified. */ public String getPathParameter(String name) { - return (pathParameters == null ? null : pathParameters.get(name)); - } - - /** - * Returns the specified path parameter converted to a POJO. - * <p> - * The type can be any POJO type convertable from a <code>String</code> (See <a class="doclink" href="package-summary.html#PojosConvertableFromString">POJOs Convertable From Strings</a>). - * <p> - * <h5 class='section'>Examples:</h5> - * <p class='bcode'> - * <jc>// Parse into an integer.</jc> - * <jk>int</jk> myparam = req.getPathParameter(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>); - * - * <jc>// Parse into an int array.</jc> - * <jk>int</jk>[] myparam = req.getPathParameter(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>); - - * <jc>// Parse into a bean.</jc> - * MyBean myparam = req.getPathParameter(<js>"myparam"</js>, MyBean.<jk>class</jk>); - * - * <jc>// Parse into a linked-list of objects.</jc> - * List myparam = req.getPathParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>); - * - * <jc>// Parse into a map of object keys/values.</jc> - * Map myparam = req.getPathParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>); - * </p> - * - * @param name The attribute name. - * @param type The class type to convert the attribute value to. - * @param <T> The class type to convert the attribute value to. - * @return The attribute value converted to the specified class type. - * @throws ParseException - */ - public <T> T getPathParameter(String name, Class<T> type) throws ParseException { - return getPathParameter(name, beanSession.getClassMeta(type)); - } - - /** - * Returns the specified path parameter converted to a POJO. - * <p> - * The type can be any POJO type convertable from a <code>String</code> (See <a class="doclink" href="package-summary.html#PojosConvertableFromString">POJOs Convertable From Strings</a>). - * <p> - * Use this method if you want to parse into a parameterized <code>Map</code>/<code>Collection</code> object. - * <p> - * <h5 class='section'>Examples:</h5> - * <p class='bcode'> - * <jc>// Parse into a linked-list of strings.</jc> - * List<String> myparam = req.getPathParameter(<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.getPathParameter(<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.getPathParameter(<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.getPathParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); - * </p> - * - * @param name The attribute 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. - * @param <T> The class type to convert the attribute value to. - * @return The attribute value converted to the specified class type. - * @throws ParseException - */ - public <T> T getPathParameter(String name, Type type, Type...args) throws ParseException { - return (T)getPathParameter(name, beanSession.getClassMeta(type, args)); - } - - /* Workhorse method */ - <T> T getPathParameter(String name, ClassMeta<T> cm) throws ParseException { - Object attr = getPathParameter(name); - T t = null; - if (attr != null) - t = urlEncodingParser.parsePart(attr.toString(), cm); - if (t == null && cm.isPrimitive()) - return cm.getPrimitiveDefault(); - return t; + return pathParams.get(name); } @@ -1139,123 +491,12 @@ public final class RestRequest extends HttpServletRequestWrapper { //-------------------------------------------------------------------------------- /** - * 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 getBody(Class<T> type) throws IOException, ParseException { - return getBody(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 getBody(Type type, Type...args) { - return (T)getBody(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. + * Returns the body of this HTTP request. * - * @return The incoming input from the connection as a plain string. - * @throws IOException If a problem occurred trying to read from the reader. + * @return The body of this HTTP request. */ - public String getBodyAsString() throws IOException { - if (body == null) - body = IOUtils.readBytes(getInputStream(), 1024); - return new String(body, IOUtils.UTF8); + public RequestBody getBody() { + return body; } /** @@ -1267,24 +508,7 @@ public final class RestRequest extends HttpServletRequestWrapper { */ @Override /* ServletRequest */ public BufferedReader getReader() throws IOException { - Reader r = getUnbufferedReader(); - if (r instanceof BufferedReader) - return (BufferedReader)r; - int len = 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(), getCharacterEncoding()); + return getBody().getReader(); } /** @@ -1298,66 +522,11 @@ public final class RestRequest extends HttpServletRequestWrapper { */ @Override /* ServletRequest */ public ServletInputStream getInputStream() throws IOException { - - if (body != null) - return new ServletInputStream2(body); - - Encoder enc = getEncoder(); - - ServletInputStream is = super.getInputStream(); - if (enc != null) { - final InputStream is2 = enc.getInputStream(is); - return new ServletInputStream2(is2); - } - return is; + return getBody().getInputStream(); } - /* Workhorse method */ - <T> T getBody(ClassMeta<T> cm) throws RestException { - - try { - if (cm.isReader()) - return (T)getReader(); - - if (cm.isInputStream()) - return (T)getInputStream(); - - TimeZone timeZone = getTimeZone(); - Locale locale = getLocale(); - ParserMatch pm = getParserMatch(); - - if (pm != null) { - Parser p = pm.getParser(); - MediaType mediaType = pm.getMediaType(); - try { - properties.append("mediaType", mediaType).append("characterEncoding", getCharacterEncoding()); - if (! p.isReaderParser()) { - InputStreamParser p2 = (InputStreamParser)p; - ParserSession session = p2.createSession(getInputStream(), properties, getJavaMethod(), context.getResource(), locale, timeZone, mediaType); - return p2.parseSession(session, cm); - } - ReaderParser p2 = (ReaderParser)p; - ParserSession session = p2.createSession(getUnbufferedReader(), properties, getJavaMethod(), context.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}", - getHeader("Content-Type"), parserGroup.getSupportedMediaTypes() - ); - - } catch (IOException e) { - throw new RestException(SC_INTERNAL_SERVER_ERROR, - "I/O exception occurred while attempting to handle request ''{0}''.", - getDescription() - ).initCause(e); - } + ServletInputStream getRawInputStream() throws IOException { + return super.getInputStream(); } @@ -1724,53 +893,6 @@ public final class RestRequest extends HttpServletRequestWrapper { return parserGroup; } - /** - * 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 = getContentTypeHeader(); - if (mediaType == null) { - if (body != null) - mediaType = MediaType.UON; - else - mediaType = MediaType.JSON; - } - ParserMatch pm = parserGroup.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; - } /** * Returns the method of this request. @@ -1782,10 +904,24 @@ public final class RestRequest extends HttpServletRequestWrapper { return method; } + /** + * Returns the HTTP 1.1 method name of the request as an enum. + * <p> + * Note that non-RFC2616 method names resolve as {@link HttpMethod#OTHER}. + * + * @return The HTTP method. + */ + public HttpMethod getHttpMethod() { + return HttpMethod.forString(method); + } @Override /* ServletRequest */ public int getContentLength() { - return contentLength == 0 ? super.getContentLength() : contentLength; + return getBody().getContentLength(); + } + + int getRawContentLength() { + return super.getContentLength(); } /** @@ -1799,7 +935,7 @@ public final class RestRequest extends HttpServletRequestWrapper { * @return <jk>true</jk> if {@code &plainText=true} was specified as a URL parameter */ public boolean isPlainText() { - return "true".equals(getQueryParameter("plainText", "false")); + return "true".equals(getQuery("plainText", "false")); } /** @@ -1976,18 +1112,17 @@ public final class RestRequest extends HttpServletRequestWrapper { sb.append("\t").append(h).append(": ").append(getHeader(h)).append("\n"); } sb.append("---Default Servlet Headers---\n"); - for (Map.Entry<String,String> e : defaultServletHeaders.entrySet()) { + for (Map.Entry<String,String> e : context.getDefaultRequestHeaders().entrySet()) { sb.append("\t").append(e.getKey()).append(": ").append(e.getValue()).append("\n"); } if (javaMethod == null) { sb.append("***init() not called yet!***\n"); } else if (method.equals("PUT") || method.equals("POST")) { try { - body = IOUtils.readBytes(getInputStream(), 1024); sb.append("---Body UTF-8---\n"); - sb.append(new String(body, "UTF-8")).append("\n"); + sb.append(body.asString()).append("\n"); sb.append("---Body Hex---\n"); - sb.append(StringUtils.toHex(body)).append("\n"); + sb.append(body.asHex()).append("\n"); } catch (Exception e1) { sb.append(e1.getLocalizedMessage()); context.getLogger().log(WARNING, e1, "Error occurred while trying to read debug input."); @@ -2001,10 +1136,6 @@ public final class RestRequest extends HttpServletRequestWrapper { // Utility methods //-------------------------------------------------------------------------------- - private <T> T parseParameter(String val, ClassMeta<T> c) throws ParseException { - return urlEncodingParser.parsePart(val, c); - } - /* * Converts an Accept-Language value entry to a Locale. */ @@ -2018,74 +1149,8 @@ public final class RestRequest extends HttpServletRequestWrapper { return new Locale(lang, country); } - private Encoder getEncoder() { - if (encoder == null) { - String ce = 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}", - 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 header value from URL-parameters or set via setHeader() meant - * to override actual header values on the request. - */ - private String getOverriddenHeader(String name) { - String h = null; - if (context.isAllowHeaderParams()) - h = getQueryParameter(name); - if (h != null) - return h; - if (overriddenHeaders != null) { - h = overriddenHeaders.get(name); - if (h != null) - return h; - } - return h; - } void setJavaMethod(Method method) { this.javaMethod = method; } - - /** - * 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(); - } - } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/FormData.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/FormData.java b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/FormData.java index 9a16812..bf40391 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/FormData.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/FormData.java @@ -46,7 +46,7 @@ import org.apache.juneau.rest.*; * * <h6 class='topic'>Important note concerning FORM posts</h6> * <p> - * This annotation should not be combined with the {@link Body @Body} annotation or {@link RestRequest#getBody(Class)} method + * This annotation should not be combined with the {@link Body @Body} annotation or {@link RestRequest#getBody()} method * for <code>application/x-www-form-urlencoded POST</code> posts, since it will trigger the underlying servlet * API to parse the body content as key-value pairs resulting in empty content. * <p> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasFormData.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasFormData.java b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasFormData.java index 79d9ba0..085d7e9 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasFormData.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasFormData.java @@ -74,7 +74,7 @@ import org.apache.juneau.rest.*; * * <h6 class='topic'>Important note concerning FORM posts</h6> * <p> - * This annotation should not be combined with the {@link Body @Body} annotation or {@link RestRequest#getBody(Class)} method + * This annotation should not be combined with the {@link Body @Body} annotation or {@link RestRequest#getBody()} method * for <code>application/x-www-form-urlencoded POST</code> posts, since it will trigger the underlying servlet API to parse the body * content as key-value pairs, resulting in empty content. * <p> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasQuery.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasQuery.java b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasQuery.java index 880b1b7..19f4469 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasQuery.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/HasQuery.java @@ -26,7 +26,7 @@ import org.apache.juneau.rest.*; * Unlike {@link HasFormData @HasFormData}, using this annotation does not result in the servlet reading the contents * of URL-encoded form posts. * Therefore, this annotation can be used in conjunction with the {@link Body @Body} annotation - * or {@link RestRequest#getBody(Class)} method for <code>application/x-www-form-urlencoded POST</code> calls. + * or {@link RestRequest#getBody()} method for <code>application/x-www-form-urlencoded POST</code> calls. * * <h5 class='section'>Example:</h5> * <p class='bcode'> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/Query.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/Query.java b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/Query.java index 8e411d3..1f00fdc 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/Query.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/Query.java @@ -26,7 +26,7 @@ import org.apache.juneau.rest.*; * Unlike {@link FormData @FormData}, using this annotation does not result in the servlet reading the contents * of URL-encoded form posts. * Therefore, this annotation can be used in conjunction with the {@link Body @Body} annotation - * or {@link RestRequest#getBody(Class)} method for <code>application/x-www-form-urlencoded POST</code> calls. + * or {@link RestRequest#getBody()} method for <code>application/x-www-form-urlencoded POST</code> calls. * * <h5 class='section'>Example:</h5> * <p class='bcode'> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java index b4bb393..cf5f16d 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Introspectable.java @@ -40,8 +40,8 @@ public final class Introspectable implements RestConverter { @Override /* RestConverter */ @SuppressWarnings({"unchecked", "rawtypes"}) public Object convert(RestRequest req, Object o, ClassMeta cm) throws RestException { - String method = req.getQueryParameter("invokeMethod"); - String args = req.getQueryParameter("invokeArgs"); + String method = req.getQuery("invokeMethod"); + String args = req.getQuery("invokeArgs"); if (method == null) return o; try { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java index fee4939..a563863 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Queryable.java @@ -58,10 +58,11 @@ public final class Queryable implements RestConverter { return null; try { + RequestQuery q = req.getQuery(); // If no actual filtering parameters have been passed in, and there is no map augmenter specified, // then just pass the original object back. - if (req.hasAnyQueryParameters("q","v","s","g","i","p","l")) { + if (q.containsAnyKeys("q","v","s","g","i","p","l")) { BeanSession session = req.getBeanSession(); if (cm.getPojoSwap() != null) @@ -70,16 +71,16 @@ public final class Queryable implements RestConverter { PojoQuery f = new PojoQuery(o, session); if (o instanceof Collection || o.getClass().isArray()) { - ObjectMap query = req.getQueryParameter("q", ObjectMap.class); - List<String> view = req.getQueryParameter("v", List.class, String.class); - List sort = req.getQueryParameter("s", List.class, String.class); - boolean ignoreCase = req.getQueryParameter("i", false, Boolean.class); - int pos = req.getQueryParameter("p", 0, Integer.class); - int limit = req.getQueryParameter("l", 0, Integer.class); + ObjectMap query = q.get("q", ObjectMap.class); + List<String> view = q.get("v", List.class, String.class); + List sort = q.get("s", List.class, String.class); + boolean ignoreCase = q.get("i", false, Boolean.class); + int pos = q.get("p", 0, Integer.class); + int limit = q.get("l", 0, Integer.class); o = f.filterCollection(query, view, sort, pos, limit, ignoreCase); } else { - List<String> view = req.getQueryParameter("v", List.class, String.class); + List<String> view = q.get("v", List.class, String.class); o = f.filterMap(view); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java index f56bf15..4816453 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java @@ -53,7 +53,7 @@ public final class Traversable implements RestConverter { try { if (cm.getPojoSwap() != null) o = cm.getPojoSwap().swap(req.getBeanSession(), o); - PojoRest p = new PojoRest(o, req.getReaderParser()); + PojoRest p = new PojoRest(o, req.getBody().getReaderParser()); o = p.get(req.getPathRemainder()); } catch (SerializeException e) { throw new RestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java b/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java index 795f521..2d173f5 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java @@ -99,9 +99,9 @@ public abstract class RemoteableServlet extends RestServletDefault { public Object invoke(RestRequest req, @Path String javaInterface, @Path String javaMethod) throws Exception { // Find the parser. - ReaderParser p = req.getReaderParser(); + ReaderParser p = req.getBody().getReaderParser(); if (p == null) - throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE, "Could not find parser for media type ''{0}''", req.getContentTypeHeader()); //$NON-NLS-1$ + throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE, "Could not find parser for media type ''{0}''", req.getHeaders().getContentType()); //$NON-NLS-1$ Class<?> c = getInterfaceClass(javaInterface); // Find the service. http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java b/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java index 190ea44..b06479b 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/response/DefaultHandler.java @@ -60,13 +60,13 @@ public class DefaultHandler implements ResponseHandler { if (! s.isWriterSerializer()) { OutputStreamSerializer s2 = (OutputStreamSerializer)s; OutputStream os = res.getNegotiatedOutputStream(); - SerializerSession session = s.createSession(os, p, req.getJavaMethod(), req.getLocale(), req.getTimeZone(), mediaType); + SerializerSession session = s.createSession(os, p, req.getJavaMethod(), req.getLocale(), req.getHeaders().getTimeZone(), mediaType); s2.serialize(session, output); os.close(); } else { WriterSerializer s2 = (WriterSerializer)s; Writer w = res.getNegotiatedWriter(); - SerializerSession session = s.createSession(w, p, req.getJavaMethod(), req.getLocale(), req.getTimeZone(), mediaType); + SerializerSession session = s.createSession(w, p, req.getJavaMethod(), req.getLocale(), req.getHeaders().getTimeZone(), mediaType); s2.serialize(session, output); w.close(); } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/321f6bde/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java b/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java index 455a375..f02c2ab 100644 --- a/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/vars/RequestVar.java @@ -72,9 +72,9 @@ public class RequestVar extends SimpleVar { 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); if ("attribute".equals(prefix))
