You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/09/08 23:25:37 UTC

svn commit: r21540 [11/27] - in /release/incubator/juneau: juneau-rest-client/ juneau-rest-client/.settings/ juneau-rest-client/bin/ juneau-rest-client/src/ juneau-rest-client/src/main/ juneau-rest-client/src/main/java/ juneau-rest-client/src/main/java...

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormData.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormData.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormData.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,372 @@
+// ***************************************************************************************************************************
+// * 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 org.apache.juneau.internal.ArrayUtils.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+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;
+	}
+
+	/**
+	 * Adds default entries to these form-data parameters.
+	 *
+	 * <p>
+	 * This includes the default form-data parameters defined on the servlet and method levels.
+	 *
+	 * @param defaultEntries The default entries.  Can be <jk>null</jk>.
+	 * @return This object (for method chaining).
+	 */
+	public RequestFormData addDefault(Map<String,String> defaultEntries) {
+		if (defaultEntries != null) {
+			for (Map.Entry<String,String> e : defaultEntries.entrySet()) {
+				String key = e.getKey(), value = e.getValue();
+				String[] v = get(key);
+				if (v == null || v.length == 0 || StringUtils.isEmpty(v[0]))
+					put(key, new String[]{value});
+			}
+		}
+		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).
+	 *
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul>
+	 * 	<li>Calling this method on URL-Encoded FORM posts causes the body content to be loaded and parsed by the
+	 * 		underlying servlet API.
+	 * 	<li>This method returns the raw unparsed value, and differs from calling
+	 * 		<code>getFormDataParameter(name, String.<jk>class</js>)</code> which will convert the value from UON
+	 * 		notation:
+	 * 		<ul>
+	 * 			<li><js>"null"</js> =&gt; <jk>null</jk>
+	 * 			<li><js>"'null'"</js> =&gt; <js>"null"</js>
+	 * 			<li><js>"'foo bar'"</js> =&gt; <js>"foo bar"</js>
+	 * 			<li><js>"foo~~bar"</js> =&gt; <js>"foo~bar"</js>
+	 * 		</ul>
+	 * </ul>
+	 *
+	 * @param name The form data parameter name.
+	 * @return The parameter value, or <jk>null</jk> if parameter does not exist.
+	 */
+	public String getString(String name) {
+		String[] v = get(name);
+		if (v == null || v.length == 0)
+			return null;
+
+		// Fix for behavior difference between Tomcat and WAS.
+		// getParameter("foo") on "&foo" in Tomcat returns "".
+		// getParameter("foo") on "&foo" in WAS returns null.
+		if (v.length == 1 && v[0] == null)
+			return "";
+
+		return v[0];
+	}
+
+	/**
+	 * Same as {@link #getString(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 parameter does not exist or is <jk>null</jk> or empty.
+	 */
+	public String getString(String name, String def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : s;
+	}
+
+	/**
+	 * Same as {@link #getString(String)} but converts the value to an integer.
+	 *
+	 * @param name The form data parameter name.
+	 * @return The parameter value, or <code>0</code> if parameter does not exist or is <jk>null</jk> or empty.
+	 */
+	public int getInt(String name) {
+		return getInt(name, 0);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to an integer.
+	 *
+	 * @param name The form data parameter name.
+	 * @param def The default value.
+	 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty.
+	 */
+	public int getInt(String name, int def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Integer.parseInt(s);
+	}
+
+	/**
+	 * Same as {@link #getString(String)} but converts the value to a boolean.
+	 *
+	 * @param name The form data parameter name.
+	 * @return The parameter value, or <jk>false</jk> if parameter does not exist or is <jk>null</jk> or empty.
+	 */
+	public boolean getBoolean(String name) {
+		return getBoolean(name, false);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to a boolean.
+	 *
+	 * @param name The form data parameter name.
+	 * @param def The default value.
+	 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty.
+	 */
+	public boolean getBoolean(String name, boolean def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s);
+	}
+
+	/**
+	 * Returns the specified form data parameter value converted to a POJO using the {@link UrlEncodingParser}
+	 * registered with this servlet.
+	 *
+	 * <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>
+	 *
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul>
+	 * 	<li>Calling this method on URL-Encoded FORM posts causes the body content to be loaded and parsed by the
+	 * 		underlying servlet API.
+	 * </ul>
+	 *
+	 * @param name The parameter name.
+	 * @param type The class type to convert the parameter value to.
+	 * @param <T> The class type to convert the parameter value to.
+	 * @return The parameter value converted to the specified class type.
+	 * @throws ParseException
+	 */
+	public <T> T get(String name, Class<T> type) throws ParseException {
+		return parse(name, beanSession.getClassMeta(type));
+	}
+
+	/**
+	 * Same as {@link #get(String, Class)} except returns a default value if not specified.
+	 *
+	 * @param name The parameter name.
+	 * @param def The default value if the parameter was not specified or is <jk>null</jk>.
+	 * @param type The class type to convert the parameter value to.
+	 * @param <T> The class type to convert the parameter value to.
+	 * @return The parameter value converted to the specified class type.
+	 * @throws ParseException
+	 */
+	public <T> T get(String name, T def, Class<T> type) throws ParseException {
+		return parse(name, def, beanSession.getClassMeta(type));
+	}
+
+	/**
+	 * Same as {@link #get(String, Class)} except for use on multi-part parameters
+	 * (e.g. <js>"key=1&amp;key=2&amp;key=3"</js> instead of <js>"key=(1,2,3)"</js>)
+	 *
+	 * <p>
+	 * This method must only be called when parsing into classes of type Collection or array.
+	 *
+	 * @param name The parameter name.
+	 * @param type The class type to convert the parameter value to.
+	 * @return The parameter value converted to the specified class type.
+	 * @throws ParseException
+	 */
+	public <T> T getAll(String name, Class<T> type) throws ParseException {
+		return parseAll(name, beanSession.getClassMeta(type));
+	}
+
+	/**
+	 * Returns the specified form data parameter value converted to a POJO using the {@link UrlEncodingParser}
+	 * registered with this servlet.
+	 *
+	 * <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>
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// Parse into a linked-list of strings.</jc>
+	 * 	List&lt;String&gt; myparam = req.getFormDataParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a linked-list of linked-lists of strings.</jc>
+	 * 	List&lt;List&lt;String&gt;&gt; myparam = req.getFormDataParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a map of string keys/values.</jc>
+	 * 	Map&lt;String,String&gt; myparam = req.getFormDataParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a map containing string keys and values of lists containing beans.</jc>
+	 * 	Map&lt;String,List&lt;MyBean&gt;&gt; myparam = req.getFormDataParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);
+	 * </p>
+	 *
+	 * @param name The parameter name.
+	 * @param type
+	 * 	The type of object to create.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType},
+	 * 	{@link GenericArrayType}
+	 * @param args
+	 * 	The type arguments of the class if it's a collection or map.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType},
+	 * 	{@link GenericArrayType}
+	 * 	<br>Ignored if the main type is not a map or collection.
+	 * @return The parameter value converted to the specified class type.
+	 * @throws ParseException
+	 */
+	public <T> T get(String name, Type type, Type...args) throws ParseException {
+		return (T)parse(name, beanSession.getClassMeta(type, args));
+	}
+
+	/**
+	 * Same as {@link #get(String, Type, Type...)} except for use on multi-part parameters
+	 * (e.g. <js>"key=1&amp;key=2&amp;key=3"</js> instead of <js>"key=(1,2,3)"</js>)
+	 *
+	 * <p>
+	 * This method must only be called when parsing into classes of type Collection or array.
+	 *
+	 * @param name The parameter name.
+	 * @param type
+	 * 	The type of object to create.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType},
+	 * 	{@link GenericArrayType}
+	 * @param args
+	 * 	The type arguments of the class if it's a collection or map.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType},
+	 * 	{@link GenericArrayType}
+	 * 	<br>Ignored if the main type is not a map or collection.
+	 * @return The parameter value converted to the specified class type.
+	 * @throws ParseException
+	 */
+	public <T> T getAll(String name, Type type, Type...args) throws ParseException {
+		return (T)parseAll(name, beanSession.getClassMeta(type, args));
+	}
+
+	/* Workhorse method */
+	<T> T parse(String name, T def, ClassMeta<T> cm) throws ParseException {
+		String val = getString(name);
+		if (val == null)
+			return def;
+		return parseValue(val, cm);
+	}
+
+	/* Workhorse method */
+	<T> T parse(String name, ClassMeta<T> cm) throws ParseException {
+		String val = getString(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)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.parse(PartType.FORM_DATA, val, c);
+	}
+
+	/**
+	 * Converts the form data parameters to a readable string.
+	 *
+	 * @param sorted Sort the form data parameters by name.
+	 * @return A JSON string containing the contents of the form data parameters.
+	 */
+	public String toString(boolean sorted) {
+		Map<String,Object> m = (sorted ? new TreeMap<String,Object>() : new LinkedHashMap<String,Object>());
+		for (Map.Entry<String,String[]> e : this.entrySet()) {
+			String[] v = e.getValue();
+			m.put(e.getKey(), v.length == 1 ? v[0] : v);
+		}
+		return JsonSerializer.DEFAULT_LAX.toString(m);
+	}
+
+	@Override /* Object */
+	public String toString() {
+		return toString(false);
+	}
+}

Propchange: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormData.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeaders.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeaders.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeaders.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,808 @@
+// ***************************************************************************************************************************
+// * 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 org.apache.juneau.internal.ArrayUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.Date;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Represents the headers in an HTTP request.
+ *
+ * <p>
+ * Entries are stored in a case-insensitive map.
+ */
+public class RequestHeaders extends TreeMap<String,String[]> {
+	private static final long serialVersionUID = 1L;
+
+	private UrlEncodingParser parser;
+	private BeanSession beanSession;
+	private RequestQuery queryParams;
+
+	RequestHeaders() {
+		super(String.CASE_INSENSITIVE_ORDER);
+	}
+
+	RequestHeaders setParser(UrlEncodingParser parser) {
+		this.parser = parser;
+		return this;
+	}
+
+	RequestHeaders setBeanSession(BeanSession beanSession) {
+		this.beanSession = beanSession;
+		return this;
+	}
+
+	RequestHeaders setQueryParams(RequestQuery queryParams) {
+		this.queryParams = queryParams;
+		return this;
+	}
+
+	/**
+	 * Adds default entries to these headers.
+	 *
+	 * <p>
+	 * This includes the default headers defined on the servlet and method levels.
+	 *
+	 * @param defaultEntries The default entries.  Can be <jk>null</jk>.
+	 * @return This object (for method chaining).
+	 */
+	public RequestHeaders addDefault(Map<String,String> defaultEntries) {
+		if (defaultEntries != null) {
+			for (Map.Entry<String,String> e : defaultEntries.entrySet()) {
+				String key = e.getKey(), value = e.getValue();
+				String[] v = get(key);
+				if (v == null || v.length == 0 || StringUtils.isEmpty(v[0]))
+					put(key, new String[]{value});
+			}
+		}
+		return this;
+	}
+
+	/**
+	 * Adds a set of header values to this object.
+	 *
+	 * @param name The header name.
+	 * @param values The header values.
+	 * @return This object (for method chaining).
+	 */
+	public RequestHeaders put(String name, Enumeration<String> values) {
+		// Optimized for enumerations of one entry, the most-common case.
+		if (values.hasMoreElements()) {
+			String v = values.nextElement();
+			String[] s = new String[]{v};
+			while (values.hasMoreElements())
+				s = append(s, values.nextElement());
+			put(name, s);
+		}
+		return this;
+	}
+
+	/**
+	 * 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.
+	 *
+	 * @param name The header name.
+	 * @return The header value, or <jk>null</jk> if it doesn't exist.
+	 */
+	public String getString(String name) {
+		String[] v = null;
+		if (queryParams != null)
+			v = queryParams.get(name);
+		if (v == null || v.length == 0)
+			v = get(name);
+		if (v == null || v.length == 0)
+			return null;
+		return v[0];
+	}
+
+	/**
+	 * 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.
+	 *
+	 * @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 getString(String name, String def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : s;
+	}
+
+	/**
+	 * Same as {@link #getString(String)} but converts the value to an integer.
+	 *
+	 * @param name The HTTP header name.
+	 * @return The header value, or the default value if the header isn't present.
+	 */
+	public int getInt(String name) {
+		return getInt(name, 0);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to an integer.
+	 *
+	 * @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 int getInt(String name, int def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Integer.parseInt(s);
+	}
+
+	/**
+	 * Same as {@link #getString(String)} but converts the value to a boolean.
+	 *
+	 * @param name The HTTP header name.
+	 * @return The header value, or the default value if the header isn't present.
+	 */
+	public boolean getBoolean(String name) {
+		return getBoolean(name, false);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to a boolean.
+	 *
+	 * @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 boolean getBoolean(String name, boolean def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s);
+	}
+
+	/**
+	 * Sets a request header value.
+	 *
+	 * @param name The header name.
+	 * @param value The header value.
+	 */
+	public void put(String name, Object value) {
+		super.put(name, new String[]{StringUtils.toString(value)});
+	}
+
+	/**
+	 * Returns the specified header value converted to a POJO.
+	 *
+	 * <p>
+	 * The type can be any POJO type convertible from a <code>String</code>
+	 * (See <a class="doclink" href="package-summary.html#PojosConvertableFromString">POJOs Convertible From Strings</a>).
+	 *
+	 * <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 get(String name, Class<T> type) {
+		String h = getString(name);
+		return beanSession.convertToType(h, type);
+	}
+
+	/**
+	 * Same as {@link #get(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 get(String name, T def, Class<T> type) {
+		String h = getString(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 convertible from a <code>String</code>
+	 * (See <a class="doclink" href="package-summary.html#PojosConvertableFromString">POJOs Convertible From Strings</a>).
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// Parse into a linked-list of strings.</jc>
+	 * 	List&lt;String&gt; 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.
+	 */
+	@SuppressWarnings("unchecked")
+	public <T> T get(String name, Type type, Type...args) throws ParseException {
+		String h = getString(name);
+		return (T)parser.parse(PartType.HEADER, h, beanSession.getClassMeta(type, args));
+	}
+
+	/**
+	 * Returns a copy of this object, but only with the specified header names copied.
+	 *
+	 * @param headers The headers to include in the copy.
+	 * @return A new headers object.
+	 */
+	public RequestHeaders subset(String...headers) {
+		RequestHeaders rh2 = new RequestHeaders().setParser(parser).setBeanSession(beanSession).setQueryParams(queryParams);
+		for (String h : headers)
+			if (containsKey(h))
+				rh2.put(h, get(h));
+		return rh2;
+	}
+
+	/**
+	 * Same as {@link #subset(String...)}, but allows you to specify header names as a comma-delimited list.
+	 *
+	 * @param headers The headers to include in the copy.
+	 * @return A new headers object.
+	 */
+	public RequestHeaders subset(String headers) {
+		return subset(split(headers));
+	}
+
+	/**
+	 * Returns the <code>Accept</code> header on the request.
+	 *
+	 * <p>
+	 * Content-Types that are acceptable for the response.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Accept: text/plain
+	 * </p>
+	 *
+	 * @return The parsed <code>Accept</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Accept getAccept() {
+		return Accept.forString(getString("Accept"));
+	}
+
+	/**
+	 * Returns the <code>Accept-Charset</code> header on the request.
+	 *
+	 * <p>
+	 * Character sets that are acceptable.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Accept-Charset: utf-8
+	 * </p>
+	 *
+	 * @return The parsed <code>Accept-Charset</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public AcceptCharset getAcceptCharset() {
+		return AcceptCharset.forString(getString("Accept-Charset"));
+	}
+
+	/**
+	 * Returns the <code>Accept-Encoding</code> header on the request.
+	 *
+	 * <p>
+	 * List of acceptable encodings.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Accept-Encoding: gzip, deflate
+	 * </p>
+	 *
+	 * @return The parsed <code>Accept-Encoding</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public AcceptEncoding getAcceptEncoding() {
+		return AcceptEncoding.forString(getString("Accept-Encoding"));
+	}
+
+	/**
+	 * Returns the <code>Accept-Language</code> header on the request.
+	 *
+	 * <p>
+	 * List of acceptable human languages for response.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Accept-Language: en-US
+	 * </p>
+	 *
+	 * @return The parsed <code>Accept-Language</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public AcceptLanguage getAcceptLanguage() {
+		return AcceptLanguage.forString(getString("Accept-Language"));
+	}
+
+	/**
+	 * Returns the <code>Authorization</code> header on the request.
+	 *
+	 * <p>
+	 * Authentication credentials for HTTP authentication.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+	 * </p>
+	 *
+	 * @return The parsed <code>Authorization</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Authorization getAuthorization() {
+		return Authorization.forString(getString("Authorization"));
+	}
+
+	/**
+	 * Returns the <code>Cache-Control</code> header on the request.
+	 *
+	 * <p>
+	 * Used to specify directives that must be obeyed by all caching mechanisms along the request-response chain.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Cache-Control: no-cache
+	 * </p>
+	 *
+	 * @return The parsed <code>Cache-Control</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public CacheControl getCacheControl() {
+		return CacheControl.forString(getString("Cache-Control"));
+	}
+
+	/**
+	 * Returns the <code>Connection</code> header on the request.
+	 *
+	 * <p>
+	 * Control options for the current connection and list of hop-by-hop request fields.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Connection: keep-alive
+	 * 	Connection: Upgrade
+	 * </p>
+	 *
+	 * @return The parsed <code></code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Connection getConnection() {
+		return Connection.forString(getString("Connection"));
+	}
+
+	/**
+	 * Returns the <code>Content-Length</code> header on the request.
+	 *
+	 * <p>
+	 * The length of the request body in octets (8-bit bytes).
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Content-Length: 348
+	 * </p>
+	 *
+	 * @return The parsed <code>Content-Length</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public ContentLength getContentLength() {
+		return ContentLength.forString(getString("Content-Length"));
+	}
+
+	/**
+	 * Returns the <code>Content-Type</code> header on the request.
+	 *
+	 * <p>
+	 * The MIME type of the body of the request (used with POST and PUT requests).
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Content-Type: application/x-www-form-urlencoded
+	 * </p>
+	 *
+	 * @return The parsed <code>Content-Type</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public ContentType getContentType() {
+		return ContentType.forString(getString("Content-Type"));
+	}
+
+	/**
+	 * Returns the <code>Date</code> header on the request.
+	 *
+	 * <p>
+	 * The date and time that the message was originated (in "HTTP-date" format as defined by RFC 7231 Date/Time Formats).
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Date: Tue, 15 Nov 1994 08:12:31 GMT
+	 * </p>
+	 *
+	 * @return The parsed <code>Date</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Date getDate() {
+		return Date.forString(getString("Date"));
+	}
+
+	/**
+	 * Returns the <code>Expect</code> header on the request.
+	 *
+	 * <p>
+	 * Indicates that particular server behaviors are required by the client.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Expect: 100-continue
+	 * </p>
+	 *
+	 * @return The parsed <code>Expect</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Expect getExpect() {
+		return Expect.forString(getString("Expect"));
+	}
+
+	/**
+	 * Returns the <code>From</code> header on the request.
+	 *
+	 * <p>
+	 * The email address of the user making the request.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	From: user@example.com
+	 * </p>
+	 *
+	 * @return The parsed <code>From</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public From getFrom() {
+		return From.forString(getString("From"));
+	}
+
+	/**
+	 * Returns the <code>Host</code> header on the request.
+	 *
+	 * <p>
+	 * The domain name of the server (for virtual hosting), and the TCP port number on which the server is listening.
+	 * The port number may be omitted if the port is the standard port for the service requested.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Host: en.wikipedia.org:8080
+	 * 	Host: en.wikipedia.org
+	 * </p>
+	 *
+	 * @return The parsed <code>Host</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Host getHost() {
+		return Host.forString(getString("Host"));
+	}
+
+	/**
+	 * Returns the <code>If-Match</code> header on the request.
+	 *
+	 * <p>
+	 * Only perform the action if the client supplied entity matches the same entity on the server.
+	 * This is mainly for methods like PUT to only update a resource if it has not been modified since the user last
+	 * updated it.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	If-Match: "737060cd8c284d8af7ad3082f209582d"
+	 * </p>
+	 *
+	 * @return The parsed <code>If-Match</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public IfMatch getIfMatch() {
+		return IfMatch.forString(getString("If-Match"));
+	}
+
+	/**
+	 * Returns the <code>If-Modified-Since</code> header on the request.
+	 *
+	 * <p>
+	 * Allows a 304 Not Modified to be returned if content is unchanged.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
+	 * </p>
+	 *
+	 * @return The parsed <code>If-Modified-Since</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public IfModifiedSince getIfModifiedSince() {
+		return IfModifiedSince.forString(getString("If-Modified-Since"));
+	}
+
+	/**
+	 * Returns the <code>If-None-Match</code> header on the request.
+	 *
+	 * <p>
+	 * Allows a 304 Not Modified to be returned if content is unchanged, see HTTP ETag.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	If-None-Match: "737060cd8c284d8af7ad3082f209582d"
+	 * </p>
+	 *
+	 * @return The parsed <code>If-None-Match</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public IfNoneMatch getIfNoneMatch() {
+		return IfNoneMatch.forString(getString("If-None-Match"));
+	}
+
+	/**
+	 * Returns the <code>If-Range</code> header on the request.
+	 *
+	 * <p>
+	 * If the entity is unchanged, send me the part(s) that I am missing; otherwise, send me the entire new entity.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	If-Range: "737060cd8c284d8af7ad3082f209582d"
+	 * </p>
+	 *
+	 * @return The parsed <code>If-Range</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public IfRange getIfRange() {
+		return IfRange.forString(getString("If-Range"));
+	}
+
+	/**
+	 * Returns the <code>If-Unmodified-Since</code> header on the request.
+	 *
+	 * <p>
+	 * Only send the response if the entity has not been modified since a specific time.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT
+	 * </p>
+	 *
+	 * @return The parsed <code>If-Unmodified-Since</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public IfUnmodifiedSince getIfUnmodifiedSince() {
+		return IfUnmodifiedSince.forString(getString("If-Unmodified-Since"));
+	}
+
+	/**
+	 * Returns the <code>Max-Forwards</code> header on the request.
+	 *
+	 * <p>
+	 * Limit the number of times the message can be forwarded through proxies or gateways.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Max-Forwards: 10
+	 * </p>
+	 *
+	 * @return The parsed <code>Max-Forwards</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public MaxForwards getMaxForwards() {
+		return MaxForwards.forString(getString("Max-Forwards"));
+	}
+
+	/**
+	 * Returns the <code>Pragma</code> header on the request.
+	 *
+	 * <p>
+	 * Implementation-specific fields that may have various effects anywhere along the request-response chain.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Pragma: no-cache
+	 * </p>
+	 *
+	 * @return The parsed <code>Pragma</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Pragma getPragma() {
+		return Pragma.forString(getString("Pragma"));
+	}
+
+	/**
+	 * Returns the <code>Proxy-Authorization</code> header on the request.
+	 *
+	 * <p>
+	 * Authorization credentials for connecting to a proxy.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+	 * </p>
+	 *
+	 * @return The parsed <code>Proxy-Authorization</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public ProxyAuthorization getProxyAuthorization() {
+		return ProxyAuthorization.forString(getString("Proxy-Authorization"));
+	}
+
+	/**
+	 * Returns the <code>Range</code> header on the request.
+	 *
+	 * <p>
+	 * Request only part of an entity. Bytes are numbered from 0.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Range: bytes=500-999
+	 * </p>
+	 *
+	 * @return The parsed <code>Range</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Range getRange() {
+		return Range.forString(getString("Range"));
+	}
+
+	/**
+	 * Returns the <code>Referer</code> header on the request.
+	 *
+	 * <p>
+	 * This is the address of the previous web page from which a link to the currently requested page was followed.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Referer: http://en.wikipedia.org/wiki/Main_Page
+	 * </p>
+	 *
+	 * @return The parsed <code>Referer</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Referer getReferer() {
+		return Referer.forString(getString("Referer"));
+	}
+
+	/**
+	 * Returns the <code>TE</code> header on the request.
+	 *
+	 * <p>
+	 * The transfer encodings the user agent is willing to accept: the same values as for the response header field
+	 * Transfer-Encoding can be used, plus the "trailers" value (related to the "chunked" transfer method) to notify the
+	 * server it expects to receive additional fields in the trailer after the last, zero-sized, chunk.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	TE: trailers, deflate
+	 * </p>
+	 *
+	 * @return The parsed <code>TE</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public TE getTE() {
+		return TE.forString(getString("TE"));
+	}
+
+	/**
+	 * 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 = getString("Time-Zone");
+		if (tz != null)
+			return TimeZone.getTimeZone(tz);
+		return null;
+	}
+
+	/**
+	 * Returns the <code>User-Agent</code> header on the request.
+	 *
+	 * <p>
+	 * The user agent string of the user agent.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0
+	 * </p>
+	 *
+	 * @return The parsed <code>User-Agent</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public UserAgent getUserAgent() {
+		return UserAgent.forString(getString("User-Agent"));
+	}
+
+	/**
+	 * Returns the <code>Upgrade</code> header on the request.
+	 *
+	 * <p>
+	 * Ask the server to upgrade to another protocol.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Upgrade: HTTP/2.0, HTTPS/1.3, IRC/6.9, RTA/x11, websocket
+	 * </p>
+	 *
+	 * @return The parsed <code>Upgrade</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Upgrade getUpgrade() {
+		return Upgrade.forString(getString("Upgrade"));
+	}
+
+	/**
+	 * Returns the <code>Via</code> header on the request.
+	 *
+	 * <p>
+	 * Informs the server of proxies through which the request was sent.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Via: 1.0 fred, 1.1 example.com (Apache/1.1)
+	 * </p>
+	 *
+	 * @return The parsed <code>Via</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Via getVia() {
+		return Via.forString(getString("Via"));
+	}
+
+	/**
+	 * Returns the <code>Warning</code> header on the request.
+	 *
+	 * <p>
+	 * A general warning about possible problems with the entity body.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	Warning: 199 Miscellaneous warning
+	 * </p>
+	 *
+	 * @return The parsed <code>Warning</code> header on the request, or <jk>null</jk> if not found.
+	 */
+	public Warning getWarning() {
+		return Warning.forString(getString("Warning"));
+	}
+
+	/**
+	 * Converts the headers to a readable string.
+	 *
+	 * @param sorted Sort the headers by name.
+	 * @return A JSON string containing the contents of the headers.
+	 */
+	public String toString(boolean sorted) {
+		Map<String,Object> m = (sorted ? new TreeMap<String,Object>() : new LinkedHashMap<String,Object>());
+		for (Map.Entry<String,String[]> e : this.entrySet()) {
+			String[] v = e.getValue();
+			m.put(e.getKey(), v.length == 1 ? v[0] : v);
+		}
+		return JsonSerializer.DEFAULT_LAX.toString(m);
+	}
+
+	@Override /* Object */
+	public String toString() {
+		return toString(false);
+	}
+}

Propchange: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeaders.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathMatch.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathMatch.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathMatch.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,222 @@
+// ***************************************************************************************************************************
+// * 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 org.apache.juneau.internal.StringUtils.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Contains information about the matched path on the HTTP request.
+ *
+ * <p>
+ * Provides access to the matched path variables and path match remainder.
+ */
+@SuppressWarnings("unchecked")
+public class RequestPathMatch extends TreeMap<String,String> {
+	private static final long serialVersionUID = 1L;
+
+	private UrlEncodingParser parser;
+	private BeanSession beanSession;
+	private String remainder;
+
+	RequestPathMatch() {
+		super(String.CASE_INSENSITIVE_ORDER);
+	}
+
+	RequestPathMatch setParser(UrlEncodingParser parser) {
+		this.parser = parser;
+		return this;
+	}
+
+	RequestPathMatch setBeanSession(BeanSession beanSession) {
+		this.beanSession = beanSession;
+		return this;
+	}
+
+	RequestPathMatch setRemainder(String remainder) {
+		this.remainder = remainder;
+		return this;
+	}
+
+	/**
+	 * Sets a request query parameter value.
+	 *
+	 * @param name The parameter name.
+	 * @param value The parameter value.
+	 */
+	public void put(String name, Object value) {
+		super.put(name, value.toString());
+	}
+
+	/**
+	 * Returns the specified path parameter converted to a POJO.
+	 *
+	 * <p>
+	 * The type can be any POJO type convertible from a <code>String</code> (See <a class="doclink"
+	 * href="package-summary.html#PojosConvertibleFromString">POJOs Convertible From Strings</a>).
+	 *
+	 * <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 get(String name, Class<T> type) throws ParseException {
+		return parse(name, beanSession.getClassMeta(type));
+	}
+
+	/**
+	 * Returns the specified path parameter converted to a POJO.
+	 *
+	 * <p>
+	 * The type can be any POJO type convertible from a <code>String</code> (See <a class="doclink"
+	 * href="package-summary.html#PojosConvertibleFromString">POJOs Convertible From Strings</a>).
+	 *
+	 * <p>
+	 * Use this method if you want to parse into a parameterized <code>Map</code>/<code>Collection</code> object.
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// Parse into a linked-list of strings.</jc>
+	 * 	List&lt;String&gt; 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&lt;List&lt;String&gt;&gt; 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&lt;String,String&gt; 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&lt;String,List&lt;MyBean&gt;&gt; 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 get(String name, Type type, Type...args) throws ParseException {
+		return (T)parse(name, beanSession.getClassMeta(type, args));
+	}
+
+	/* Workhorse method */
+	<T> T parse(String name, ClassMeta<T> cm) throws ParseException {
+		Object attr = get(name);
+		T t = null;
+		if (attr != null)
+			t = parser.parse(PartType.PATH, attr.toString(), cm);
+		if (t == null && cm.isPrimitive())
+			return cm.getPrimitiveDefault();
+		return t;
+	}
+
+	/**
+	 * Returns the decoded remainder of the URL following any path pattern matches.
+	 *
+	 * <p>
+	 * The behavior of path remainder is shown below given the path pattern "/foo/*":
+	 * <table class='styled'>
+	 * 	<tr>
+	 * 		<th>URL</th>
+	 * 		<th>Path Remainder</th>
+	 * 	</tr>
+	 * 	<tr>
+	 * 		<td><code>/foo</code></td>
+	 * 		<td><jk>null</jk></td>
+	 * 	</tr>
+	 * 	<tr>
+	 * 		<td><code>/foo/</code></td>
+	 * 		<td><js>""</js></td>
+	 * 	</tr>
+	 * 	<tr>
+	 * 		<td><code>/foo//</code></td>
+	 * 		<td><js>"/"</js></td>
+	 * 	</tr>
+	 * 	<tr>
+	 * 		<td><code>/foo///</code></td>
+	 * 		<td><js>"//"</js></td>
+	 * 	</tr>
+	 * 	<tr>
+	 * 		<td><code>/foo/a/b</code></td>
+	 * 		<td><js>"a/b"</js></td>
+	 * 	</tr>
+	 * 	<tr>
+	 * 		<td><code>/foo//a/b/</code></td>
+	 * 		<td><js>"/a/b/"</js></td>
+	 * 	</tr>
+	 * 	<tr>
+	 * 		<td><code>/foo/a%2Fb</code></td>
+	 * 		<td><js>"a/b"</js></td>
+	 * 	</tr>
+	 * </table>
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// REST method</jc>
+	 * 	<ja>@RestMethod</ja>(name=<js>"GET"</js>,path=<js>"/foo/{bar}/*"</js>)
+	 * 	<jk>public</jk> String doGetById(RequestPathParams pathParams, <jk>int</jk> bar) {
+	 * 		<jk>return</jk> pathParams.getRemainder();
+	 * 	}
+	 *
+	 * 	<jc>// Prints "path/remainder"</jc>
+	 * 	<jk>new</jk> RestCall(servletPath + <js>"/foo/123/path/remainder"</js>).connect();
+	 * </p>
+	 *
+	 * @return The path remainder string.
+	 */
+	public String getRemainder() {
+		return urlDecode(remainder);
+	}
+
+	/**
+	 * Same as {@link #getRemainder()} but doesn't decode characters.
+	 *
+	 * @return The un-decoded path remainder.
+	 */
+	public String getRemainderUndecoded() {
+		return remainder;
+	}
+}

Propchange: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathMatch.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQuery.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQuery.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQuery.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,505 @@
+// ***************************************************************************************************************************
+// * 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 org.apache.juneau.internal.ArrayUtils.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.servlet.http.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.utils.*;
+import org.apache.juneau.xml.*;
+
+/**
+ * Represents the query parameters in an HTTP request.
+ */
+@SuppressWarnings("unchecked")
+public final class RequestQuery extends LinkedHashMap<String,String[]> {
+	private static final long serialVersionUID = 1L;
+
+	private UrlEncodingParser parser;
+	private BeanSession beanSession;
+
+	RequestQuery setParser(UrlEncodingParser parser) {
+		this.parser = parser;
+		return this;
+	}
+
+	RequestQuery setBeanSession(BeanSession beanSession) {
+		this.beanSession = beanSession;
+		return this;
+	}
+
+	/**
+	 * Create a copy of the request query parameters.
+	 */
+	RequestQuery copy() {
+		RequestQuery rq = new RequestQuery();
+		rq.putAll(this);
+		return rq;
+	}
+
+	/**
+	 * Adds default entries to these query parameters.
+	 *
+	 * <p>
+	 * This includes the default queries defined on the servlet and method levels.
+	 *
+	 * @param defaultEntries The default entries.  Can be <jk>null</jk>.
+	 * @return This object (for method chaining).
+	 */
+	public RequestQuery addDefault(Map<String,String> defaultEntries) {
+		if (defaultEntries != null) {
+			for (Map.Entry<String,String> e : defaultEntries.entrySet()) {
+				String key = e.getKey(), value = e.getValue();
+				String[] v = get(key);
+				if (v == null || v.length == 0 || StringUtils.isEmpty(v[0]))
+					put(key, new String[]{value});
+			}
+		}
+		return this;
+	}
+
+	/**
+	 * Sets a request query parameter value.
+	 *
+	 * @param name The parameter name.
+	 * @param value The parameter value.
+	 */
+	public void put(String name, Object value) {
+		put(name, new String[]{StringUtils.toString(value)});
+	}
+
+	/**
+	 * Returns a query parameter value.
+	 *
+	 * <p>
+	 * Same as {@link HttpServletRequest#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.
+	 *
+	 * <p>
+	 * If multiple query parameters have the same name, this returns only the first instance.
+	 *
+	 * @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>"&amp;foo"</js>.
+	 */
+	public String getString(String name) {
+		String[] v = get(name);
+		if (v == null || v.length == 0)
+			return null;
+
+		// Fix for behavior difference between Tomcat and WAS.
+		// getParameter("foo") on "&foo" in Tomcat returns "".
+		// getParameter("foo") on "&foo" in WAS returns null.
+		if (v.length == 1 && v[0] == null)
+			return "";
+
+		return v[0];
+	}
+
+	/**
+	 * Same as {@link #getString(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>"&amp;foo"</js>.
+	 */
+	public String getString(String name, String def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : s;
+	}
+
+	/**
+	 * Same as {@link #getString(String)} but converts the value to an integer.
+	 *
+	 * @param name The URL parameter name.
+	 * @return
+	 * 	The parameter value, or <code>0</code> if parameter not specified or has no value
+	 * 	(e.g. <js>"&amp;foo"</js>.
+	 */
+	public int getInt(String name) {
+		return getInt(name, 0);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to an integer.
+	 *
+	 * @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>"&amp;foo"</js>.
+	 */
+	public int getInt(String name, int def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Integer.parseInt(s);
+	}
+
+	/**
+	 * Same as {@link #getString(String)} but converts the value to a boolean.
+	 *
+	 * @param name The URL parameter name.
+	 * @return
+	 * 	The parameter value, or <jk>false</jk> if parameter not specified or has no value
+	 * 	(e.g. <js>"&amp;foo"</js>.
+	 */
+	public boolean getBoolean(String name) {
+		return getBoolean(name, false);
+	}
+
+	/**
+	 * Same as {@link #getString(String,String)} but converts the value to a boolean.
+	 *
+	 * @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>"&amp;foo"</js>.
+	 */
+	public boolean getBoolean(String name, boolean def) {
+		String s = getString(name);
+		return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(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.
+	 *
+	 * <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 get(String name, Class<T> type) throws ParseException {
+		return get(name, beanSession.getClassMeta(type));
+	}
+
+	/**
+	 * Same as {@link #get(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 get(String name, T def, Class<T> type) throws ParseException {
+		return get(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.
+	 *
+	 * <p>
+	 * Use this method if you want to parse into a parameterized <code>Map</code>/<code>Collection</code> object.
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// Parse into a linked-list of strings.</jc>
+	 * 	List&lt;String&gt; 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>
+	 * 	List&lt;List&lt;String&gt;&gt; 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&lt;String,String&gt; 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&lt;String,List&lt;MyBean&gt;&gt; myparam = req.getQueryParameter(<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.
+	 * @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, Type type, Type...args) throws ParseException {
+		return (T)parse(name, beanSession.getClassMeta(type, args));
+	}
+
+	/**
+	 * Same as {@link #get(String, Class)} except returns a default value if not found.
+	 *
+	 * @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 get(String name, Object def, Type type, Type...args) throws ParseException {
+		return (T)parse(name, def, beanSession.getClassMeta(type, args));
+	}
+
+	/**
+	 * Same as {@link #get(String, Class)} except for use on multi-part parameters
+	 * (e.g. <js>"&amp;key=1&amp;key=2&amp;key=3"</js> instead of <js>"&amp;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 getAll(String name, Class<T> c) throws ParseException {
+		return getAll(name, beanSession.getClassMeta(c));
+	}
+
+	/**
+	 * Same as {@link #get(String, Type, Type...)} except for use on multi-part parameters
+	 * (e.g. <js>"&amp;key=1&amp;key=2&amp;key=3"</js> instead of <js>"&amp;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
+	 */
+	public <T> T getAll(String name, Type type, Type...args) throws ParseException {
+		return (T)parseAll(name, beanSession.getClassMeta(type, args));
+	}
+
+	/**
+	 * 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 containsAnyKeys(String...params) {
+		for (String p : params)
+			if (containsKey(p))
+				return true;
+		return false;
+	}
+
+	/**
+	 * Locates the special search query arguments in the query and returns them as a {@link SearchArgs} object.
+	 *
+	 * <p>
+	 * The query arguments are as follows:
+	 * <ul>
+	 * 	<li>
+	 * 		<js>"&amp;s="</js> - A comma-delimited list of column-name/search-token pairs.
+	 * 		<br>Example: <js>"&amp;s=column1=foo*,column2=*bar"</js>
+	 * 	<li>
+	 * 		<js>"&amp;v="</js> - A comma-delimited list column names to view.
+	 * 		<br>Example: <js>"&amp;v=column1,column2"</js>
+	 * 	<li>
+	 * 		<js>"&amp;o="</js> - A comma-delimited list column names to sort by.
+	 * 		<br>Column names can be suffixed with <js>'-'</js> to indicate descending order.
+	 * 		<br>Example: <js>"&amp;o=column1,column2-"</js>
+	 * 	<li>
+	 * 		<js>"&amp;p="</js> - The zero-index row number of the first row to display.
+	 * 		<br>Example: <js>"&amp;p=100"</js>
+	 * 	<li>
+	 * 		<js>"&amp;l="</js> - The number of rows to return.
+	 * 		<br><code>0</code> implies return all rows.
+	 * 		<br>Example: <js>"&amp;l=100"</js>
+	 * 	<li>
+	 * 		<js>"&amp;i="</js> - The case-insensitive search flag.
+	 * 		<br>Example: <js>"&amp;i=true"</js>
+	 * </ul>
+	 *
+	 * <p>
+	 * Whitespace is trimmed in the parameters.
+	 *
+	 * @return
+	 * 	A new {@link SearchArgs} object initialized with the special search query arguments.
+	 * 	<jk>null</jk> if no search arguments were found.
+	 */
+	public SearchArgs getSearchArgs() {
+		if (hasAny("s","v","o","p","l","i")) {
+			return new SearchArgs.Builder()
+				.search(getString("s"))
+				.view(getString("v"))
+				.sort(getString("o"))
+				.position(getInt("p"))
+				.limit(getInt("l"))
+				.ignoreCase(getBoolean("i"))
+				.build();
+		}
+		return null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the query parameters contains any of the specified names.
+	 *
+	 * @param paramNames The parameter names to check for.
+	 * @return <jk>true</jk> if the query parameters contains any of the specified names.
+	 */
+	public boolean hasAny(String...paramNames) {
+		for (String p : paramNames)
+			if (containsKey(p))
+				return true;
+		return false;
+	}
+
+	/* Workhorse method */
+	private <T> T parse(String name, T def, ClassMeta<T> cm) throws ParseException {
+		String val = getString(name);
+		if (val == null)
+			return def;
+		return parseValue(val, cm);
+	}
+
+	/* Workhorse method */
+	private <T> T parse(String name, ClassMeta<T> cm) throws ParseException {
+		String val = getString(name);
+		if (cm.isPrimitive() && (val == null || val.isEmpty()))
+			return cm.getPrimitiveDefault();
+		return parseValue(val, cm);
+	}
+
+	/* Workhorse method */
+	@SuppressWarnings("rawtypes")
+	private <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)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 getQueryParameters(String, ClassMeta).  Class type must be a Collection or array.");
+	}
+
+	private <T> T parseValue(String val, ClassMeta<T> c) throws ParseException {
+		return parser.parse(PartType.QUERY, val, c);
+	}
+
+	/**
+	 * Converts the query parameters to a readable string.
+	 *
+	 * @param sorted Sort the query parameters by name.
+	 * @return A JSON string containing the contents of the query parameters.
+	 */
+	public String toString(boolean sorted) {
+		Map<String,Object> m = (sorted ? new TreeMap<String,Object>() : new LinkedHashMap<String,Object>());
+		for (Map.Entry<String,String[]> e : this.entrySet()) {
+			String[] v = e.getValue();
+			m.put(e.getKey(), v.length == 1 ? v[0] : v);
+		}
+		return JsonSerializer.DEFAULT_LAX.toString(m);
+	}
+
+	/**
+	 * Converts this object to a query string.
+	 *
+	 * <p>
+	 * Returned query string does not start with <js>'?'</js>.
+	 *
+	 * @return A new query string, or an empty string if this object is empty.
+	 */
+	public String toQueryString() {
+		StringBuilder sb = new StringBuilder();
+		for (Map.Entry<String,String[]> e : this.entrySet()) {
+			for (int i = 0; i < e.getValue().length; i++) {
+				if (sb.length() > 0)
+					sb.append("&");
+				sb.append(XmlUtils.urlEncode(e.getKey())).append('=').append(XmlUtils.urlEncode(e.getValue()[i]));
+			}
+		}
+		return sb.toString();
+	}
+
+	@Override /* Object */
+	public String toString() {
+		return toString(false);
+	}
+}

Propchange: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQuery.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseHandler.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseHandler.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseHandler.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,112 @@
+// ***************************************************************************************************************************
+// * 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.io.*;
+
+import javax.servlet.http.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.response.*;
+
+/**
+ * Defines the interface for handlers that convert POJOs to appropriate HTTP responses.
+ *
+ * <p>
+ * The {@link RestServlet} API uses the concept of registered response handlers for converting objects returned by REST
+ * methods or set through {@link RestResponse#setOutput(Object)} into appropriate HTTP responses.
+ *
+ * <p>
+ * Response handlers can be associated with {@link RestServlet RestServlets} through the following ways:
+ * <ul class='spaced-list'>
+ * 	<li>
+ * 		Through the {@link RestResource#responseHandlers @RestResource.responseHandlers} annotation.
+ * 	<li>
+ * 		By calling the {@link RestConfig#addResponseHandlers(Class...)} and augmenting or creating your
+ * 		own list of handlers.
+ * </ul>
+ *
+ * <p>
+ * By default, {@link RestServlet RestServlets} are registered with the following response handlers:
+ * <ul class='spaced-list'>
+ * 	<li>
+ * 		{@link DefaultHandler} - Serializes POJOs using the Juneau serializer API.
+ * 	<li>
+ * 		{@link ReaderHandler} - Pipes the output of {@link Reader Readers} to the response writer
+ * 		({@link RestResponse#getWriter()}).
+ * 	<li>
+ * 		{@link InputStreamHandler} - Pipes the output of {@link InputStream InputStreams} to the response output
+ * 		stream ({@link RestResponse#getOutputStream()}).
+ * 	<li>
+ * 		{@link RedirectHandler} - Handles {@link Redirect} objects.
+ * 	<li>
+ * 		{@link WritableHandler} - Handles {@link Writable} objects.
+ * 	<li>
+ * 		{@link StreamableHandler} - Handles {@link Streamable} objects.
+ * </ul>
+ *
+ * <p>
+ * Response handlers can be used to process POJOs that cannot normally be handled through Juneau serializers, or
+ * because it's simply easier to define response handlers for special cases.
+ *
+ * <p>
+ * The following example shows how to create a response handler to handle special <code>Foo</code> objects outside the
+ * normal Juneau architecture.
+ * <p class='bcode'>
+ * 	<ja>@RestResource</ja>(
+ * 		path=<js>"/example"</js>,
+ * 		responseHandlers=FooHandler.<jk>class</jk>
+ * 	)
+ * 	<jk>public class</jk> Example <jk>extends</jk> RestServlet {
+ *
+ * 		<ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/"</js>)
+ * 		<jk>public</jk> Foo test1() {
+ * 			<jk>return new</jk> Foo(<js>"123"</js>);
+ * 		}
+ *
+ * 		<jk>public static class</jk> FooHandler <jk>implements</jk> ResponseHandler {
+ * 			<ja>@Override</ja>
+ * 			<jk>public boolean</jk> handle(RestRequest req, RestResponse res, Object output) <jk>throws</jk> IOException, RestException {
+ * 				<jk>if</jk> (output <jk>instanceof</jk> Foo) {
+ * 					Foo foo = (Foo)output;
+ * 					<jc>// Set some headers and body content.</jc>
+ * 					res.setHeader(<js>"Foo-ID"</js>, foo.getId());
+ * 					res.getWriter().write(<js>"foo.id="</js> + foo.getId());
+ * 					<jk>return true</jk>;  <jc>// We handled it.</jc>
+ * 				}
+ * 				<jk>return false</jk>;  <jc>// We didn't handle it.</jc>
+ * 			}
+ * 		}
+ * 	}
+ * </p>
+ */
+public interface ResponseHandler {
+
+	/**
+	 * Process this response if possible.
+	 * This method should return <jk>false</jk> if it wasn't able to process the response.
+	 *
+	 * @param req The HTTP servlet request.
+	 * @param res The HTTP servlet response;
+	 * @param output The POJO returned by the REST method that now needs to be sent to the response.
+	 * @return true If this handler handled the response.
+	 * @throws IOException
+	 * 	If low-level exception occurred on output stream.
+	 * 	Results in a {@link HttpServletResponse#SC_INTERNAL_SERVER_ERROR} error.
+	 * @throws RestException
+	 * 	If some other exception occurred.
+	 * 	Can be used to provide an appropriate HTTP response code and message.
+	 */
+	boolean handle(RestRequest req, RestResponse res, Object output) throws IOException, RestException;
+}

Propchange: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain