This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new bd06ea3  REST refactoring.
bd06ea3 is described below

commit bd06ea39d2dd2f1731c2200d655ed6be9b3bc136
Author: JamesBognar <[email protected]>
AuthorDate: Sun Feb 21 11:51:39 2021 -0500

    REST refactoring.
---
 .../java/org/apache/juneau/rest/RequestPath.java   | 357 --------------
 .../org/apache/juneau/rest/RequestPathParam.java   | 293 ++++++++++++
 .../org/apache/juneau/rest/RequestPathParams.java  | 512 +++++++++++++++++++++
 .../apache/juneau/rest/RestOperationContext.java   |  13 -
 .../java/org/apache/juneau/rest/RestRequest.java   |  59 +--
 .../apache/juneau/rest/annotation/HookEvent.java   |   2 +-
 .../org/apache/juneau/rest/annotation/Rest.java    |   2 +-
 .../java/org/apache/juneau/rest/args/PathArg.java  |  12 +-
 .../apache/juneau/rest/args/RequestPathArg.java    |  10 +-
 .../apache/juneau/rest/converters/Traversable.java |   2 +-
 .../apache/juneau/rest/vars/RequestPathVar.java    |   6 +-
 .../http/remote/Remote_PathAnnotation_Test.java    |  10 +-
 .../java/org/apache/juneau/rest/Paths_Test.java    |   4 +-
 .../org/apache/juneau/rest/RestOp_Params_Test.java |   2 +-
 .../apache/juneau/rest/annotation/Path_Test.java   |  28 +-
 15 files changed, 869 insertions(+), 443 deletions(-)

diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
deleted file mode 100644
index fa15203..0000000
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
+++ /dev/null
@@ -1,357 +0,0 @@
-// 
***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
-// * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
-// * with the License.  You may obtain a copy of the License at                
                                              *
-// *                                                                           
                                              *
-// *  http://www.apache.org/licenses/LICENSE-2.0                               
                                              *
-// *                                                                           
                                              *
-// * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
-// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
-// * specific language governing permissions and limitations under the 
License.                                              *
-// 
***************************************************************************************************************************
-package org.apache.juneau.rest;
-
-import static org.apache.juneau.internal.StringUtils.*;
-
-import java.lang.reflect.*;
-import java.util.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.collections.*;
-import org.apache.juneau.httppart.*;
-import org.apache.juneau.oapi.*;
-import org.apache.juneau.parser.*;
-import org.apache.juneau.http.exception.*;
-
-/**
- * Contains information about the matched path on the HTTP request.
- *
- * <p>
- * Provides access to the matched path variables and path match remainder.
- *
- * <ul class='seealso'>
- *     <li class='link'>{@doc RestmRequestPathMatch}
- * </ul>
- */
-public class RequestPath extends TreeMap<String,String> {
-       private static final long serialVersionUID = 1L;
-
-       private final RestRequest req;
-       private HttpPartParserSession parser;
-
-       RequestPath(RestRequest req) {
-               super(String.CASE_INSENSITIVE_ORDER);
-               this.req = req;
-       }
-
-       RequestPath parser(HttpPartParserSession parser) {
-               this.parser = parser;
-               return this;
-       }
-
-       RequestPath remainder(String remainder) {
-               put("/**", remainder);
-               put("/*", urlDecode(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 String.
-        *
-        * @param name The path variable name.
-        * @return The parameter value.
-        * @throws BadRequest Thrown if input could not be parsed.
-        * @throws InternalServerError Thrown if any other exception occurs.
-        */
-       public String getString(String name) throws BadRequest, 
InternalServerError {
-               return getInner(parser, null, name, null, 
req.getBeanSession().string());
-       }
-
-       /**
-        * Returns the specified path parameter converted to an integer.
-        *
-        * @param name The path variable name.
-        * @return The parameter value.
-        * @throws BadRequest Thrown if input could not be parsed.
-        * @throws InternalServerError Thrown if any other exception occurs.
-        */
-       public int getInt(String name) throws BadRequest, InternalServerError {
-               return getInner(parser, null, name, null, 
getClassMeta(int.class));
-       }
-
-       /**
-        * Returns the specified path parameter converted to a boolean.
-        *
-        * @param name The path variable name.
-        * @return The parameter value.
-        * @throws BadRequest Thrown if input could not be parsed.
-        * @throws InternalServerError Thrown if any other exception occurs.
-        */
-       public boolean getBoolean(String name) throws BadRequest, 
InternalServerError {
-               return getInner(null, null, name, null, 
getClassMeta(boolean.class));
-       }
-
-       /**
-        * Returns the specified path parameter value converted to a POJO using 
the {@link HttpPartParser} registered with the resource.
-        *
-        * <h5 class='section'>Examples:</h5>
-        * <p class='bcode w800'>
-        *      <jc>// Parse into an integer.</jc>
-        *      <jk>int</jk> <jv>myparam</jv> = 
<jv>path</jv>.get(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>);
-        *
-        *      <jc>// Parse into an int array.</jc>
-        *      <jk>int</jk>[] <jv>myparam</jv> = 
<jv>path</jv>.get(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>);
-
-        *      <jc>// Parse into a bean.</jc>
-        *      MyBean <jv>myparam</jv> = <jv>path</jv>.get(<js>"myparam"</js>, 
MyBean.<jk>class</jk>);
-        *
-        *      <jc>// Parse into a linked-list of objects.</jc>
-        *      List <jv>myparam</jv> = <jv>path</jv>.get(<js>"myparam"</js>, 
LinkedList.<jk>class</jk>);
-        *
-        *      <jc>// Parse into a map of object keys/values.</jc>
-        *      Map <jv>myparam</jv> = <jv>path</jv>.get(<js>"myparam"</js>, 
TreeMap.<jk>class</jk>);
-        * </p>
-        *
-        * <ul class='seealso'>
-        *      <li class='jf'>{@link RestContext#REST_partParser}
-        * </ul>
-        *
-        * @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 BadRequest Thrown if input could not be parsed.
-        * @throws InternalServerError Thrown if any other exception occurs.
-        */
-       public <T> T get(String name, Class<T> type) throws BadRequest, 
InternalServerError {
-               return getInner(null, null, name, null, getClassMeta(type));
-       }
-
-       /**
-        * Same as {@link #get(String, Class)} but allows you to override the 
part parser.
-        *
-        * @param parser
-        *      The parser to use for parsing the string value.
-        *      <br>If <jk>null</jk>, uses the part parser defined on the 
resource/method.
-        * @param schema
-        *      The schema object that defines the format of the input.
-        *      <br>If <jk>null</jk>, defaults to the schema defined on the 
parser.
-        *      <br>If that's also <jk>null</jk>, defaults to {@link 
HttpPartSchema#DEFAULT}.
-        *      <br>Only used if parser is schema-aware (e.g. {@link 
OpenApiParser}).
-        * @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 BadRequest Thrown if input could not be parsed or fails 
schema validation.
-        * @throws InternalServerError Thrown if any other exception occurs.
-        */
-       public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, 
String name, Class<T> type) throws BadRequest, InternalServerError {
-               return getInner(parser, schema, name, null, getClassMeta(type));
-       }
-
-       /**
-        * Returns the specified query parameter value converted to a POJO 
using the {@link HttpPartParser} registered with the resource.
-        *
-        * <p>
-        * Similar to {@link #get(String,Class)} but allows for complex 
collections of POJOs to be created.
-        *
-        * <p>
-        * Use this method if you want to parse into a parameterized 
<c>Map</c>/<c>Collection</c> object.
-        *
-        * <h5 class='section'>Examples:</h5>
-        * <p class='bcode w800'>
-        *      <jc>// Parse into a linked-list of strings.</jc>
-        *      List&lt;String&gt; <jv>myparam</jv> = 
<jv>req</jv>.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; <jv>myparam</jv> = 
<jv>req</jv>.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; <jv>myparam</jv> = 
<jv>req</jv>.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; <jv>myparam</jv> = 
<jv>req</jv>.getPathParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, 
String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);
-        * </p>
-        *
-        * <ul class='notes'>
-        *      <li>
-        *              <c>Collections</c> must be followed by zero or one 
parameter representing the value type.
-        *      <li>
-        *              <c>Maps</c> must be followed by zero or two parameters 
representing the key and value types.
-        * </ul>
-        *
-        * <ul class='seealso'>
-        *      <li class='jf'>{@link RestContext#REST_partParser}
-        * </ul>
-        *
-        * @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 BadRequest Thrown if input could not be parsed.
-        * @throws InternalServerError Thrown if any other exception occurs.
-        */
-       public <T> T get(String name, Type type, Type...args) throws 
BadRequest, InternalServerError {
-               return getInner(null, null, name, null, getClassMeta(type, 
args));
-       }
-
-       /**
-        * Same as {@link #get(String, Type, Type...)} but allows you to 
override the part parser.
-        *
-        * @param parser
-        *      The parser to use for parsing the string value.
-        *      <br>If <jk>null</jk>, uses the part parser defined on the 
resource/method.
-        * @param schema
-        *      The schema object that defines the format of the input.
-        *      <br>If <jk>null</jk>, defaults to the schema defined on the 
parser.
-        *      <br>If that's also <jk>null</jk>, defaults to {@link 
HttpPartSchema#DEFAULT}.
-        *      <br>Only used if parser is schema-aware (e.g. {@link 
OpenApiParser}).
-        * @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 BadRequest Thrown if input could not be parsed or fails 
schema validation.
-        * @throws InternalServerError Thrown if any other exception occurs.
-        */
-       public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, 
String name, Type type, Type...args) throws BadRequest, InternalServerError {
-               return getInner(parser, schema, name, null, getClassMeta(type, 
args));
-       }
-
-       /* Workhorse method */
-       private <T> T getInner(HttpPartParserSession parser, HttpPartSchema 
schema, String name, T def, ClassMeta<T> cm) throws BadRequest, 
InternalServerError {
-               if (parser == null)
-                       parser = req.getPartParserSession();
-               try {
-                       if (cm.isMapOrBean() && isOneOf(name, "*", "")) {
-                               OMap m = new OMap();
-                               for (Map.Entry<String,String> e : 
this.entrySet()) {
-                                       String k = e.getKey();
-                                       HttpPartSchema pschema = schema == null 
? null : schema.getProperty(k);
-                                       ClassMeta<?> cm2 = cm.getValueType();
-                                       m.put(k, getInner(parser, pschema, k, 
null, cm2));
-                               }
-                               return req.getBeanSession().convertToType(m, 
cm);
-                       }
-                       T t = parse(parser, schema, get(name), cm);
-                       return (t == null ? def : t);
-               } catch (SchemaValidationException e) {
-                       throw new BadRequest(e, "Validation failed on path 
parameter ''{0}''. ", name);
-               } catch (ParseException e) {
-                       throw new BadRequest(e, "Could not parse path parameter 
''{0}''.", name) ;
-               } catch (Exception e) {
-                       throw new InternalServerError(e, "Could not parse path 
parameter ''{0}''.", name) ;
-               }
-       }
-
-       /* Workhorse method */
-       private <T> T parse(HttpPartParserSession parser, HttpPartSchema 
schema, String val, ClassMeta<T> cm) throws SchemaValidationException, 
ParseException {
-               if (parser == null)
-                       parser = this.parser;
-               return parser.parse(HttpPartType.PATH, schema, val, cm);
-       }
-
-       /**
-        * 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><c>/foo</c></td>
-        *              <td><jk>null</jk></td>
-        *      </tr>
-        *      <tr>
-        *              <td><c>/foo/</c></td>
-        *              <td><js>""</js></td>
-        *      </tr>
-        *      <tr>
-        *              <td><c>/foo//</c></td>
-        *              <td><js>"/"</js></td>
-        *      </tr>
-        *      <tr>
-        *              <td><c>/foo///</c></td>
-        *              <td><js>"//"</js></td>
-        *      </tr>
-        *      <tr>
-        *              <td><c>/foo/a/b</c></td>
-        *              <td><js>"a/b"</js></td>
-        *      </tr>
-        *      <tr>
-        *              <td><c>/foo//a/b/</c></td>
-        *              <td><js>"/a/b/"</js></td>
-        *      </tr>
-        *      <tr>
-        *              <td><c>/foo/a%2Fb</c></td>
-        *              <td><js>"a/b"</js></td>
-        *      </tr>
-        * </table>
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bcode w800'>
-        *      <jc>// REST method</jc>
-        *      <ja>@RestGet</ja>(<js>"/foo/{bar}/*"</js>)
-        *      <jk>public</jk> String doGetById(RequestPathMatch 
<jv>path</jv>, <jk>int</jk> <jv>bar</jv>) {
-        *              <jk>return</jk> <jv>path</jv>.getRemainder();
-        *      }
-        * </p>
-        *
-        * <p>
-        * The remainder can also be retrieved by calling 
<code>get(<js>"/*"</js>)</code>.
-        *
-        * @return The path remainder string.
-        */
-       public String getRemainder() {
-               return get("/*");
-       }
-
-       /**
-        * Same as {@link #getRemainder()} but doesn't decode characters.
-        *
-        * <p>
-        * The undecoded remainder can also be retrieved by calling 
<code>get(<js>"/**"</js>)</code>.
-        *
-        * @return The un-decoded path remainder.
-        */
-       public String getRemainderUndecoded() {
-               return get("/**");
-       }
-
-       
//-----------------------------------------------------------------------------------------------------------------
-       // Helper methods
-       
//-----------------------------------------------------------------------------------------------------------------
-
-       private <T> ClassMeta<T> getClassMeta(Type type, Type...args) {
-               return req.getBeanSession().getClassMeta(type, args);
-       }
-
-       private <T> ClassMeta<T> getClassMeta(Class<T> type) {
-               return req.getBeanSession().getClassMeta(type);
-       }
-}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParam.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParam.java
new file mode 100644
index 0000000..b8c404d
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParam.java
@@ -0,0 +1,293 @@
+// 
***************************************************************************************************************************
+// * 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.httppart.HttpPartType.*;
+
+import java.time.*;
+import java.util.*;
+
+import org.apache.http.*;
+import org.apache.juneau.*;
+import org.apache.juneau.assertions.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.pair.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.reflect.*;
+
+/**
+ * Represents a single path parameter on an HTTP request.
+ */
+public class RequestPathParam extends RequestHttpPart implements NameValuePair 
{
+
+       private final String value;
+
+       /**
+        * Constructor.
+        *
+        * @param request The request object.
+        * @param name The parameter name.
+        * @param value The parameter value.
+        */
+       public RequestPathParam(RestRequest request, String name, String value) 
{
+               super(PATH, request, name);
+               this.value = value;
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Retrievers
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Override /* RequestHttpPart */
+       public String getValue() {
+               return value;
+       }
+
+       /**
+        * Returns the value of this parameter as an integer.
+        *
+        * @return The value of this parameter as an integer, or {@link 
Optional#empty()} if the parameter was not present.
+        */
+       public Optional<Integer> asInteger() {
+               return asNamedInteger().asInteger();
+       }
+
+       /**
+        * Returns the value of this parameter as a boolean.
+        *
+        * @return The value of this parameter as a boolean, or {@link 
Optional#empty()} if the parameter was not present.
+        */
+       public Optional<Boolean> asBoolean() {
+               return asNamedBoolean().asBoolean();
+       }
+
+       /**
+        * Returns the value of this parameter as a long.
+        *
+        * @return The value of this parameter as a long, or {@link 
Optional#empty()} if the parameter was not present.
+        */
+       public Optional<Long> asLong() {
+               return asNamedLong().asLong();
+       }
+
+       /**
+        * Returns the value of this parameter as a date.
+        *
+        * @return The value of this parameter as a date, or {@link 
Optional#empty()} if the parameter was not present.
+        */
+       public Optional<ZonedDateTime> asDate() {
+               return asNamedDate().asZonedDateTime();
+       }
+
+       /**
+        * Returns the value of this parameter as a list from a comma-delimited 
string.
+        *
+        * @return The value of this parameter as a list from a comma-delimited 
string, or {@link Optional#empty()} if the parameter was not present.
+        */
+       public Optional<List<String>> asCsvArray() {
+               return asNamedCsvArray().asList();
+       }
+
+       /**
+        * Returns the value of this parameter as a {@link BasicNameValuePair}.
+        *
+        * @param c The subclass of {@link BasicNameValuePair} to instantiate.
+        * @param <T> The subclass of {@link BasicNameValuePair} to instantiate.
+        * @return The value of this parameter as a string, never <jk>null</jk>.
+        */
+       public <T extends BasicNameValuePair> T asNameValuePair(Class<T> c) {
+               try {
+                       ClassInfo ci = ClassInfo.of(c);
+                       ConstructorInfo cc = 
ci.getConstructor(Visibility.PUBLIC, String.class);
+                       if (cc != null)
+                               return cc.invoke(orElse(null));
+                       cc = ci.getConstructor(Visibility.PUBLIC, String.class, 
String.class);
+                       if (cc != null)
+                               return cc.invoke(getName(), orElse(null));
+               } catch (Exception e) {
+                       throw new RuntimeException(e);
+               }
+               throw new BasicRuntimeException("Could not determine a method 
to construct type {0}", c.getClass().getName());
+       }
+
+       /**
+        * Returns the value of this parameter as a {@link BasicNamedCsvArray}.
+        *
+        * @return The value of this parameter as a {@link BasicNamedCsvArray}, 
never <jk>null</jk>.
+        */
+       public BasicNamedCsvArray asNamedCsvArray() {
+               return new BasicNamedCsvArray(getName(), getValue());
+       }
+
+       /**
+        * Returns the value of this parameter as a {@link BasicNamedDate}.
+        *
+        * @return The value of this parameter as a {@link BasicNamedDate}, 
never <jk>null</jk>.
+        */
+       public BasicNamedDate asNamedDate() {
+               return new BasicNamedDate(getName(), getValue());
+       }
+
+       /**
+        * Returns the value of this parameter as a {@link BasicNamedInteger}.
+        *
+        * @return The value of this parameter as a {@link BasicNamedInteger}, 
never <jk>null</jk>.
+        */
+       public BasicNamedInteger asNamedInteger() {
+               return new BasicNamedInteger(getName(), getValue());
+       }
+
+       /**
+        * Returns the value of this parameter as a {@link BasicNamedBoolean}.
+        *
+        * @return The value of this parameter as a {@link BasicNamedBoolean}, 
never <jk>null</jk>.
+        */
+       public BasicNamedBoolean asNamedBoolean() {
+               return new BasicNamedBoolean(getName(), getValue());
+       }
+
+       /**
+        * Returns the value of this parameter as a {@link BasicNamedLong}.
+        *
+        * @return The value of this parameter as a {@link BasicNamedLong}, 
never <jk>null</jk>.
+        */
+       public BasicNamedLong asNamedLong() {
+               return new BasicNamedLong(getName(), getValue());
+       }
+
+       /**
+        * Returns the value of this parameter as a {@link BasicNamedString}.
+        *
+        * @return The value of this parameter as a {@link BasicNamedString}, 
never <jk>null</jk>.
+        */
+       public BasicNamedString asNamedString() {
+               return new BasicNamedString(getName(), getValue());
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Assertions.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Provides the ability to perform fluent-style assertions on this 
parameter.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jv>request</jv>
+        *              .getPathParam(<js>"foo"</js>)
+        *              .assertString().contains(<js>"bar"</js>);
+        * </p>
+        *
+        * <p>
+        * The assertion test returns the original object allowing you to chain 
multiple requests like so:
+        * <p class='bcode w800'>
+        *      String <jv>foo</jv> = <jv>request</jv>
+        *              .getPathParam(<js>"foo"</js>)
+        *              .assertString().contains(<js>"bar"</js>)
+        *              .asString().get();
+        * </p>
+        *
+        * @return A new fluent assertion object.
+        */
+       public FluentStringAssertion<RequestPathParam> assertString() {
+               return new FluentStringAssertion<>(orElse(null), this);
+       }
+
+       /**
+        * Provides the ability to perform fluent-style assertions on an 
integer parameter.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jv>request</jv>
+        *              .getPathParam(<js>"age"</js>)
+        *              .assertInteger().isGreaterThan(1);
+        * </p>
+        *
+        * @return A new fluent assertion object.
+        */
+       public FluentIntegerAssertion<RequestPathParam> assertInteger() {
+               return new 
FluentIntegerAssertion<>(asNamedInteger().asInteger().orElse(null), this);
+       }
+
+       /**
+        * Provides the ability to perform fluent-style assertions on a long 
parameter.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jv>request</jv>
+        *              .getPathParam(<js>"length"</js>)
+        *              .assertLong().isLessThan(100000);
+        * </p>
+        *
+        * @return A new fluent assertion object.
+        */
+       public FluentLongAssertion<RequestPathParam> assertLong() {
+               return new 
FluentLongAssertion<>(asNamedLong().asLong().orElse(null), this);
+       }
+
+       /**
+        * Provides the ability to perform fluent-style assertions on a date 
parameter.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jv>request</jv>
+        *              .getPathParam(<js>"time"</js>)
+        *              .assertDate().isAfterNow();
+        * </p>
+        *
+        * @return A new fluent assertion object.
+        */
+       public FluentZonedDateTimeAssertion<RequestPathParam> assertDate() {
+               return new 
FluentZonedDateTimeAssertion<>(asNamedDate().asZonedDateTime().orElse(null), 
this);
+       }
+
+       /**
+        * Provides the ability to perform fluent-style assertions on 
comma-separated string parameters.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jv>request</jv>
+        *              .getPathParam(<js>"allow"</js>)
+        *              .assertCsvArray().contains(<js>"GET"</js>);
+        * </p>
+        *
+        * @return A new fluent assertion object.
+        */
+       public FluentListAssertion<RequestPathParam> assertCsvArray() {
+               return new 
FluentListAssertion<>(asNamedCsvArray().asList().orElse(null), this);
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Header passthrough methods.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Override /* Object */
+       public String toString() {
+               return getName() + "=" + getValue();
+       }
+
+       // <FluentSetters>
+
+       @Override /* GENERATED */
+       public RequestPathParam schema(HttpPartSchema value) {
+               super.schema(value);
+               return this;
+       }
+
+       @Override /* GENERATED */
+       public RequestPathParam parser(HttpPartParserSession value) {
+               super.parser(value);
+               return this;
+       }
+       // </FluentSetters>
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParams.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParams.java
new file mode 100644
index 0000000..9811e77
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParams.java
@@ -0,0 +1,512 @@
+// 
***************************************************************************************************************************
+// * 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 static java.util.Collections.*;
+import static org.apache.juneau.assertions.Assertions.*;
+
+import java.time.*;
+import java.util.*;
+
+import org.apache.http.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.rest.util.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.collections.*;
+
+/**
+ * Represents the path parameters in an HTTP request.
+ */
+@BeanIgnore
+public class RequestPathParams {
+
+       private final RestCall call;
+       private final RestRequest req;
+       private final boolean caseSensitive;
+       private HttpPartParserSession parser;
+
+       private List<RequestPathParam> list = new LinkedList<>();
+       private Map<String,List<RequestPathParam>> map = new TreeMap<>();
+
+       RequestPathParams(RestCall call, RestRequest req, boolean 
caseSensitive) {
+               this.call = call;
+               this.req = req;
+               this.caseSensitive = caseSensitive;
+
+               // Add parameters from parent context if any.
+               @SuppressWarnings("unchecked")
+               Map<String,String> parentVars = 
(Map<String,String>)req.getAttribute("juneau.pathVars").orElse(null);
+               if (parentVars != null)
+                       for (Map.Entry<String,String> e : parentVars.entrySet())
+                               add(e.getKey(), e.getValue());
+
+               UrlPathMatch pm = call.getUrlPathMatch();
+               if (pm != null) {
+                       for (Map.Entry<String,String> e : 
pm.getVars().entrySet())
+                               add(e.getKey(), e.getValue());
+                       String r = pm.getRemainder();
+                       if (r != null) {
+                               add("/**", r);
+                               add("/*", urlDecode(r));
+                       }
+               }
+       }
+
+       /**
+        * Copy constructor.
+        */
+       private RequestPathParams(RequestPathParams copyFrom) {
+               call = copyFrom.call;
+               req = copyFrom.req;
+               caseSensitive = copyFrom.caseSensitive;
+               parser = copyFrom.parser;
+               list.addAll(copyFrom.list);
+               map.putAll(copyFrom.map);
+       }
+
+       RequestPathParams parser(HttpPartParserSession parser) {
+               this.parser = parser;
+               for (RequestPathParam p : list)
+                       p.parser(parser);
+               return this;
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Basic operations.
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Adds default entries to these parameters.
+        *
+        * <p>
+        * Similar to {@link #set(String, Object)} but doesn't override 
existing values.
+        *
+        * @param pairs
+        *      The default entries.
+        *      <br>Can be <jk>null</jk>.
+        * @return This object (for method chaining).
+        */
+       public RequestPathParams addDefault(List<NameValuePair> pairs) {
+               for (NameValuePair p : pairs) {
+                       String name = p.getName();
+                       String key = key(name);
+                       List<RequestPathParam> l = map.get(key);
+                       boolean hasAllBlanks = l != null && 
l.stream().allMatch(x -> StringUtils.isEmpty(x.getValue()));
+                       if (l == null || hasAllBlanks) {
+                               if (hasAllBlanks)
+                                       list.removeAll(l);
+                               RequestPathParam x = new RequestPathParam(req, 
name, p.getValue());
+                               list.add(x);
+                               map.put(key, AList.of(x));
+                       }
+               }
+               return this;
+       }
+
+       /**
+        * Returns all the parameters with the specified name.
+        *
+        * @param name The parameter name.
+        * @return The list of all parameters with the specified name, or an 
empty list if none are found.
+        */
+       public List<RequestPathParam> getAll(String name) {
+               assertArgNotNull("name", name);
+               List<RequestPathParam> l = map.get(key(name));
+               return unmodifiableList(l == null ? emptyList() : l);
+       }
+
+       /**
+        * Returns all the parameters on this request.
+        *
+        * @return All the parameters on this request.
+        */
+       public List<RequestPathParam> getAll() {
+               return unmodifiableList(list);
+       }
+
+       /**
+        * Returns <jk>true</jk> if the parameters with the specified names are 
present.
+        *
+        * @param names The parameter names.  Must not be <jk>null</jk>.
+        * @return <jk>true</jk> if the parameters with the specified names are 
present.
+        */
+       public boolean contains(String...names) {
+               assertArgNotNull("names", names);
+               for (String n : names)
+                       if (! map.containsKey(key(n)))
+                               return false;
+               return true;
+       }
+
+       /**
+        * Returns <jk>true</jk> if the parameter with any of the specified 
names are present.
+        *
+        * @param names The parameter names.  Must not be <jk>null</jk>.
+        * @return <jk>true</jk> if the parameter with any of the specified 
names are present.
+        */
+       public boolean containsAny(String...names) {
+               assertArgNotNull("names", names);
+               for (String n : names)
+                       if (map.containsKey(key(n)))
+                               return true;
+               return false;
+       }
+
+       /**
+        * Returns <jk>true</jk> if these parameters are empty.
+        *
+        * @return <jk>true</jk> if these parameters are empty.
+        */
+       public boolean isEmpty() {
+               return list.isEmpty();
+       }
+
+       /**
+        * Adds a parameter value.
+        *
+        * <p>
+        * Parameter is added to the end.
+        * <br>Existing parameter with the same name are not changed.
+        *
+        * @param name The parameter name.  Must not be <jk>null</jk>.
+        * @param value The parameter value.
+        * @return This object (for method chaining).
+        */
+       public RequestPathParams add(String name, Object value) {
+               assertArgNotNull("name", name);
+               String key = key(name);
+               RequestPathParam h = new RequestPathParam(req, name, 
stringify(value)).parser(parser);
+               if (map.containsKey(key))
+                       map.get(key).add(h);
+               else
+                       map.put(key, AList.of(h));
+               list.add(h);
+               return this;
+       }
+
+       /**
+        * Adds request parameter values.
+        *
+        * <p>
+        * Parameters are added to the end.
+        * <br>Existing parameters with the same name are not changed.
+        *
+        * @param parameters The parameter objects.  Must not be <jk>null</jk>.
+        * @return This object (for method chaining).
+        */
+       public RequestPathParams add(NameValuePair...parameters) {
+               assertArgNotNull("parameters", parameters);
+               for (NameValuePair p : parameters) {
+                       if (p != null)
+                               add(p.getName(), p.getValue());
+               }
+               return this;
+       }
+
+       /**
+        * Sets a parameter value.
+        *
+        * <p>
+        * Parameter is added to the end.
+        * <br>Any previous parameters with the same name are removed.
+        *
+        * @param name The parameter name.  Must not be <jk>null</jk>.
+        * @param value
+        *      The parameter value.
+        *      <br>Converted to a string using {@link Object#toString()}.
+        *      <br>Can be <jk>null</jk>.
+        * @return This object (for method chaining).
+        */
+       public RequestPathParams set(String name, Object value) {
+               assertArgNotNull("name", name);
+               String key = key(name);
+               RequestPathParam p = new RequestPathParam(req, name, 
stringify(value)).parser(parser);
+               if (map.containsKey(key))
+                       
list.removeIf(x->caseSensitive?x.getName().equals(name):x.getName().equalsIgnoreCase(name));
+               list.add(p);
+               map.put(key, AList.of(p));
+               return this;
+       }
+
+
+       /**
+        * Sets request header values.
+        *
+        * <p>
+        * Parameters are added to the end of the headers.
+        * <br>Any previous parameters with the same name are removed.
+        *
+        * @param parameters The parameters to set.  Must not be <jk>null</jk> 
or contain <jk>null</jk>.
+        * @return This object (for method chaining).
+        */
+       public RequestPathParams set(NameValuePair...parameters) {
+               assertArgNotNull("headers", parameters);
+               for (NameValuePair p : parameters)
+                       remove(p);
+               for (NameValuePair p : parameters)
+                       add(p);
+               return this;
+       }
+
+       /**
+        * Remove parameters.
+        *
+        * @param name The parameter names.  Must not be <jk>null</jk>.
+        * @return This object (for method chaining).
+        */
+       public RequestPathParams remove(String...name) {
+               assertArgNotNull("name", name);
+               for (String n : name) {
+                       String key = key(n);
+                       if (map.containsKey(key))
+                               list.removeAll(map.get(key));
+                       map.remove(key);
+               }
+               return this;
+       }
+
+       /**
+        * Remove parameters.
+        *
+        * @param parameters The parameters to remove.  Must not be 
<jk>null</jk>.
+        * @return This object (for method chaining).
+        */
+       public RequestPathParams remove(NameValuePair...parameters) {
+               for (NameValuePair p : parameters)
+                       remove(p.getName());
+               return this;
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Convenience getters.
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Returns the first parameter with the specified name.
+        *
+        * <p>
+        * Note that this method never returns <jk>null</jk> and that {@link 
RequestPathParam#isPresent()} can be used
+        * to test for the existence of the parameter.
+        *
+        * @param name The parameter name.
+        * @return The parameter.  Never <jk>null</jk>.
+        */
+       public RequestPathParam getFirst(String name) {
+               assertArgNotNull("name", name);
+               List<RequestPathParam> l = map.get(key(name));
+               return (l == null || l.isEmpty() ? new RequestPathParam(req, 
name, null).parser(parser) : l.get(0));
+       }
+
+       /**
+        * Returns the last parameter with the specified name.
+        *
+        * <p>
+        * Note that this method never returns <jk>null</jk> and that {@link 
RequestPathParam#isPresent()} can be used
+        * to test for the existence of the parameter.
+        *
+        * @param name The parameter name.
+        * @return The parameter.  Never <jk>null</jk>.
+        */
+       public RequestPathParam getLast(String name) {
+               assertArgNotNull("name", name);
+               List<RequestPathParam> l = map.get(key(name));
+               return (l == null || l.isEmpty() ? new RequestPathParam(req, 
name, null).parser(parser) : l.get(l.size()-1));
+       }
+
+       /**
+        * Returns the last parameter with the specified name.
+        *
+        * <p>
+        * This is equivalent to {@link #getLast(String)}.
+        *
+        * @param name The parameter name.
+        * @return The parameter value, or {@link Optional#empty()} if it 
doesn't exist.
+        */
+       public RequestPathParam get(String name) {
+               return getLast(name);
+       }
+
+       /**
+        * Returns the last parameter with the specified name as a string.
+        *
+        * @param name The parameter name.
+        * @return The parameter value, or {@link Optional#empty()} if it 
doesn't exist.
+        */
+       public Optional<String> getString(String name) {
+               return getLast(name).asString();
+       }
+
+       /**
+        * Returns the last parameter with the specified name as an integer.
+        *
+        * @param name The parameter name.
+        * @return The parameter value, or {@link Optional#empty()} if it 
doesn't exist.
+        */
+       public Optional<Integer> getInteger(String name) {
+               return getLast(name).asInteger();
+       }
+
+       /**
+        * Returns the last parameter with the specified name as a boolean.
+        *
+        * @param name The parameter name.
+        * @return The parameter value, or {@link Optional#empty()} if it 
doesn't exist.
+        */
+       public Optional<Boolean> getBoolean(String name) {
+               return getLast(name).asBoolean();
+       }
+
+       /**
+        * Returns the last parameter with the specified name as a list from a 
comma-delimited string.
+        *
+        * @param name The parameter name.
+        * @return The parameter value, or {@link Optional#empty()} if it 
doesn't exist.
+        */
+       public Optional<List<String>> getCsvArray(String name) {
+               return getLast(name).asCsvArray();
+       }
+
+       /**
+        * Returns the last parameter with the specified name as a long.
+        *
+        * @param name The parameter name.
+        * @return The parameter value, or {@link Optional#empty()} if it 
doesn't exist.
+        */
+       public Optional<Long> getLong(String name) {
+               return getLast(name).asLong();
+       }
+
+       /**
+        * Returns the last parameter with the specified name as a boolean.
+        *
+        * @param name The parameter name.
+        * @return The parameter value, or {@link Optional#empty()} if it 
doesn't exist.
+        */
+       public Optional<ZonedDateTime> getDate(String name) {
+               return getLast(name).asDate();
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Other methods.
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Makes a copy of these parameters.
+        *
+        * @return A new parameters object.
+        */
+       public RequestPathParams copy() {
+               return new RequestPathParams(this);
+       }
+
+       /**
+        * 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><c>/foo</c></td>
+        *              <td><jk>null</jk></td>
+        *      </tr>
+        *      <tr>
+        *              <td><c>/foo/</c></td>
+        *              <td><js>""</js></td>
+        *      </tr>
+        *      <tr>
+        *              <td><c>/foo//</c></td>
+        *              <td><js>"/"</js></td>
+        *      </tr>
+        *      <tr>
+        *              <td><c>/foo///</c></td>
+        *              <td><js>"//"</js></td>
+        *      </tr>
+        *      <tr>
+        *              <td><c>/foo/a/b</c></td>
+        *              <td><js>"a/b"</js></td>
+        *      </tr>
+        *      <tr>
+        *              <td><c>/foo//a/b/</c></td>
+        *              <td><js>"/a/b/"</js></td>
+        *      </tr>
+        *      <tr>
+        *              <td><c>/foo/a%2Fb</c></td>
+        *              <td><js>"a/b"</js></td>
+        *      </tr>
+        * </table>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// REST method</jc>
+        *      <ja>@RestGet</ja>(<js>"/foo/{bar}/*"</js>)
+        *      <jk>public</jk> String doGetById(RequestPathParams 
<jv>path</jv>, <jk>int</jk> <jv>bar</jv>) {
+        *              <jk>return</jk> 
<jv>path</jv>.remainder().orElse(<jk>null</jk>);
+        *      }
+        * </p>
+        *
+        * <p>
+        * The remainder can also be retrieved by calling 
<code>get(<js>"/**"</js>)</code>.
+        *
+        * @return The path remainder string.
+        */
+       public RequestPathParam getRemainder() {
+               return get("/*");
+
+       }
+
+       /**
+        * Same as {@link #getRemainder()} but doesn't decode characters.
+        *
+        * <p>
+        * The undecoded remainder can also be retrieved by calling 
<code>get(<js>"/*"</js>)</code>.
+        *
+        * @return The un-decoded path remainder.
+        */
+       public RequestPathParam getRemainderUndecoded() {
+               return get("/**");
+       }
+
+       /**
+        * Converts the parameters to a readable string.
+        *
+        * @param sorted Sort the parameters by name.
+        * @return A JSON string containing the contents of the parameters.
+        */
+       public String toString(boolean sorted) {
+               OMap m = OMap.create();
+               if (sorted) {
+                       for (List<RequestPathParam> p1 : map.values())
+                               for (RequestPathParam p2 : p1)
+                                       m.append(p2.getName(), p2.getValue());
+               } else {
+                       for (RequestPathParam p : list)
+                               m.append(p.getName(), p.getValue());
+               }
+               return m.toString();
+       }
+
+       private String key(String name) {
+               return caseSensitive ? name : name.toLowerCase();
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return toString(false);
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperationContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperationContext.java
index 040de8f..324b8d6 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperationContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperationContext.java
@@ -1838,22 +1838,9 @@ public class RestOperationContext extends BeanContext 
implements Comparable<Rest
 
                call.restOperationContext(this);
 
-               UrlPathMatch pm = call.getUrlPathMatch();
-               if (pm == null)
-                       pm = matchPattern(call);
-
-               if (pm == null)
-                       throw new NotFound();
-
                RestRequest req = call.getRestRequest();
                RestResponse res = call.getRestResponse();
 
-               RequestPath rp = req.getPathMatch();
-               for (Map.Entry<String,String> e : pm.getVars().entrySet())
-                       rp.put(e.getKey(), e.getValue());
-               if (pm.getRemainder() != null)
-                       rp.remainder(pm.getRemainder());
-
                context.preCall(call);
 
                call.logger(callLogger);
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 3fc81ec..f585979 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -115,7 +115,7 @@ public final class RestRequest {
        private final RequestBody body;
        private final BeanSession beanSession;
        private final RequestQueryParams queryParams;
-       private final RequestPath pathParams;
+       private final RequestPathParams pathParams;
        private final RequestHeaders headers;
        private final RequestAttributes attrs;
        private final HttpPartSerializerSession partSerializerSession;
@@ -159,8 +159,7 @@ public final class RestRequest {
                        }
                }
 
-               pathParams = new RequestPath(this);
-               pathParams.putAll(call.getPathVars());
+               pathParams = new RequestPathParams(call, this, true);
 
                beanSession = opContext.createSession();
 
@@ -1202,10 +1201,10 @@ public final class RestRequest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        /**
-        * Request path match.
+        * Path parameters.
         *
         * <p>
-        * Returns a {@link RequestPath} object that encapsulates access to 
everything related to the URL path.
+        * Returns a {@link RequestPathParams} object that encapsulates access 
to URL path parameters.
         *
         * <h5 class='section'>Example:</h5>
         * <p class='bcode w800'>
@@ -1213,57 +1212,47 @@ public final class RestRequest {
         *      <jk>public void</jk> doGet(RestRequest <jv>req</jv>) {
         *
         *              <jc>// Get access to path data.</jc>
-        *              RequestPathMatch <jv>pathMatch</jv> = 
<jv>req</jv>.getPathMatch();
+        *              RequestPathParams <jv>pathParams</jv> = 
<jv>req</jv>.getPathParams();
         *
         *              <jc>// Example URL:  /123/qux/true/quux</jc>
         *
-        *              <jk>int</jk> <jv>foo</jv> = 
<jv>pathMatch</jv>.getInt(<js>"foo"</js>);  <jc>// =123</jc>
-        *              String <jv>bar</jv> = 
<jv>pathMatch</jv>.getString(<js>"bar"</js>);  <jc>// =qux</jc>
-        *              <jk>boolean</jk> <jv>baz</jv> = 
<jv>pathMatch</jv>.getBoolean(<js>"baz"</js>);  <jc>// =true</jc>
-        *              String <jv>remainder</jv> = 
<jv>pathMatch</jv>.getRemainder();  <jc>// =quux</jc>
+        *              <jk>int</jk> <jv>foo</jv> = 
<jv>pathParams</jv>.get(<js>"foo"</js>).asInteger().orElse(-1);  <jc>// 
=123</jc>
+        *              String <jv>bar</jv> = 
<jv>pathParams</jv>.get(<js>"bar"</js>).orElse(null);  <jc>// =qux</jc>
+        *              <jk>boolean</jk> <jv>baz</jv> = 
<jv>pathParams</jv>.get(<js>"baz"</js>).asBoolean().orElse(<jk>false</jk>);  
<jc>// =true</jc>
+        *              String <jv>remainder</jv> = 
<jv>pathParams</jv>.getRemainder().orElse(<jk>null</jk>);  <jc>// =quux</jc>
         *      }
         * </p>
         *
         * <ul class='notes'>
         *      <li>
         *              This object is modifiable.
-        *      <li>
-        *              Values are converted from strings using the registered 
{@link RestContext#REST_partParser part-parser} on the resource class.
-        *      <li>
-        *              The {@link RequestPath} object can also be passed as a 
parameter on the method.
-        *      <li>
-        *              The {@link Path @Path} annotation can be used to access 
individual values.
-        * </ul>
-        *
-        * <ul class='seealso'>
-        *      <li class='link'>{@doc RestmRequestPathMatch}
         * </ul>
         *
         * @return
-        *      The path data from the URL.
+        *      The path parameters.
         *      <br>Never <jk>null</jk>.
         */
-       public RequestPath getPathMatch() {
+       public RequestPathParams getPathParams() {
                return pathParams;
        }
 
        /**
-        * Shortcut for calling <c>getPathMatch().get(name)</c>.
+        * Shortcut for calling <c>getPathParams().get(<jv>name</jv>)</c>.
         *
-        * @param name The path variable name.
-        * @return The path variable value, or <jk>null</jk> if not found.
+        * @param name The path parameter name.
+        * @return The path parameter, never <jk>null</jk>.
         */
-       public String getPath(String name) {
-               return getPathMatch().get(name);
+       public RequestPathParam getPathParam(String name) {
+               return pathParams.get(name);
        }
 
        /**
-        * Shortcut for calling <c>getPathMatch().getRemainder()</c>.
+        * Shortcut for calling <c>getPathParams().getRemainder()</c>.
         *
-        * @return The path remainder value, or <jk>null</jk> if not found.
+        * @return The path remainder value, never <jk>null</jk>.
         */
-       public String getPathRemainder() {
-               return getPathMatch().getRemainder();
+       public RequestPathParam getPathRemainder() {
+               return pathParams.getRemainder();
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -1959,13 +1948,13 @@ public final class RestRequest {
                                                        if (pt == 
HttpPartType.BODY)
                                                                return 
getBody().schema(schema).asType(type);
                                                        if (pt == QUERY)
-                                                               return 
getQueryParams().get(name).parser(pp).schema(schema).asType(type);
+                                                               return 
getQueryParam(name).parser(pp).schema(schema).asType(type).orElse(null);
                                                        if (pt == FORMDATA)
-                                                               return 
getFormParams().get(name).parser(pp).schema(schema).asType(type);
+                                                               return 
getFormParam(name).parser(pp).schema(schema).asType(type).orElse(null);
                                                        if (pt == HEADER)
-                                                               return 
getHeader(name).parser(pp).schema(schema).asType(type);
+                                                               return 
getHeader(name).parser(pp).schema(schema).asType(type).orElse(null);
                                                        if (pt == PATH)
-                                                               return 
getPathMatch().get(pp, schema, name, type);
+                                                               return 
getPathParam(name).parser(pp).schema(schema).asType(type).orElse(null);
                                                }
                                                return null;
                                        }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/HookEvent.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/HookEvent.java
index f0a908d..5c3f4d3 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/HookEvent.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/HookEvent.java
@@ -154,12 +154,12 @@ public enum HookEvent {
         *                      <li>{@link RequestHeaders}
         *                      <li>{@link RequestQueryParams}
         *                      <li>{@link RequestFormParams}
+        *                      <li>{@link RequestPathParams}
         *                      <li>{@link Logger}
         *                      <li>{@link RestContext}
         *                      <li>{@link org.apache.juneau.parser.Parser}
         *                      <li>{@link Locale}
         *                      <li>{@link Swagger}
-        *                      <li>{@link RequestPath}
         *                      <li>{@link RequestBody}
         *                      <li>{@link Config}
         *                      <li>{@link UriContext}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
index f7bcdf0..257a80b 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
@@ -831,7 +831,7 @@ public @interface Rest {
         * <h5 class='topic'>Path variables</h5>
         * <p>
         * The path can contain variables that get resolved to {@link 
org.apache.juneau.http.annotation.Path @Path} parameters
-        * or access through the {@link RestRequest#getPathMatch()} method.
+        * or access through the {@link RestRequest#getPathParams()} method.
         *
         * <h5 class='figure'>Example:</h5>
         * <p class='bcode'>
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/PathArg.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/PathArg.java
index e24e85d..0f8f752 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/PathArg.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/PathArg.java
@@ -18,6 +18,7 @@ import static org.apache.juneau.internal.StringUtils.*;
 import java.lang.reflect.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.collections.*;
 import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.internal.*;
@@ -28,10 +29,6 @@ import org.apache.juneau.rest.util.*;
 
 /**
  * Resolves method parameters and parameter types annotated with {@link Path} 
on {@link RestOp}-annotated Java methods.
- *
- * <p>
- * The parameter value is resolved using <c><jv>call</jv>.{@link 
RestCall#getRestRequest() getRestRequest}().{@link RestRequest#getPathMatch() 
getPathMatch}().{@link 
RequestPath#get(HttpPartParserSession,HttpPartSchema,String,Type,Type...) 
get}(<jv>parserSession<jv>, <jv>schema</jv>, <jv>name</jv>, <jv>type</jv>)</c>
- * with a {@link HttpPartSchema schema} derived from the {@link Path} 
annotation.
  */
 public class PathArg implements RestOperationArg {
        private final HttpPartParser partParser;
@@ -102,7 +99,12 @@ public class PathArg implements RestOperationArg {
        @Override /* RestOperationArg */
        public Object resolve(RestCall call) throws Exception {
                RestRequest req = call.getRestRequest();
+               if (name.equals("*")) {
+                       OMap m = new OMap();
+                       
call.getRestRequest().getPathParams().getAll().stream().forEach(x -> 
m.put(x.getName(), x.getValue()));
+                       return req.getBeanSession().convertToType(m, type);
+               }
                HttpPartParserSession ps = partParser == null ? 
req.getPartParserSession() : 
partParser.createPartSession(req.getParserSessionArgs());
-               return call.getRestRequest().getPathMatch().get(ps, schema, 
name, type);
+               return 
call.getRestRequest().getPathParams().get(name).parser(ps).schema(schema).asType(type).orElse(null);
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/RequestPathArg.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/RequestPathArg.java
index 014cfb0..f26f79b 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/RequestPathArg.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/RequestPathArg.java
@@ -17,10 +17,10 @@ import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 
 /**
- * Resolves method of type with {@link RequestPath} on {@link 
RestOp}-annotated Java methods.
+ * Resolves method of type with {@link RequestPathParams} on {@link 
RestOp}-annotated Java methods.
  *
  * <p>
- * The parameter value is resolved using <c><jv>call</jv>.{@link 
RestCall#getRestRequest() getRestRequest}().{@link RestRequest#getPathMatch() 
getPathMatch}()</c>.
+ * The parameter value is resolved using <c><jv>call</jv>.{@link 
RestCall#getRestRequest() getRestRequest}().{@link RestRequest#getPathParams() 
getPathParams}()</c>.
  */
 public class RequestPathArg extends SimpleRestOperationArg {
 
@@ -28,10 +28,10 @@ public class RequestPathArg extends SimpleRestOperationArg {
         * Static creator.
         *
         * @param paramInfo The Java method parameter being resolved.
-        * @return A new {@link RequestPathArg}, or <jk>null</jk> if the 
parameter type is not {@link RequestPath}.
+        * @return A new {@link RequestPathArg}, or <jk>null</jk> if the 
parameter type is not {@link RequestPathParams}.
         */
        public static RequestPathArg create(ParamInfo paramInfo) {
-               if (paramInfo.isType(RequestPath.class))
+               if (paramInfo.isType(RequestPathParams.class))
                        return new RequestPathArg();
                return null;
        }
@@ -40,6 +40,6 @@ public class RequestPathArg extends SimpleRestOperationArg {
         * Constructor.
         */
        protected RequestPathArg() {
-               super((c)->c.getRestRequest().getPathMatch());
+               super((c)->c.getRestRequest().getPathParams());
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/converters/Traversable.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/converters/Traversable.java
index 0f587b5..0d6e8f5 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/converters/Traversable.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/converters/Traversable.java
@@ -49,7 +49,7 @@ public final class Traversable implements RestConverter {
                if (o == null)
                        return null;
 
-               String pathRemainder = req.getPathMatch().getRemainder();
+               String pathRemainder = req.getPathRemainder().orElse(null);
 
                if (pathRemainder != null) {
                        try {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/RequestPathVar.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/RequestPathVar.java
index 2a35fc9..ff2b4f2 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/RequestPathVar.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/RequestPathVar.java
@@ -23,7 +23,7 @@ import org.apache.juneau.svl.*;
  * The format for this var is <js>"$RP{key1[,key2...]}"</js>.
  *
  * <p>
- * Used to resolve values returned by {@link RestRequest#getPath(String)}.
+ * Used to resolve values returned by {@link RestRequest#getPathParam(String)}.
  * <br>When multiple keys are used, returns the first non-null/empty value.
  *
  * <h5 class='section'>Example:</h5>
@@ -70,8 +70,8 @@ public class RequestPathVar extends MultipartResolvingVar {
        public String resolve(VarResolverSession session, String key) {
                RestRequest req = 
session.getBean(RestRequest.class).orElseThrow(InternalServerError::new);
                if ("REMAINDER".equals(key))
-                       return req.getPathRemainder();
-               return req.getPath(key);
+                       return req.getPathParams().getRemainder().orElse(null);
+               return req.getPathParam(key).orElse(null);
        }
 
        @Override /* Var */
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_PathAnnotation_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_PathAnnotation_Test.java
index d0aa05f..278dce6 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_PathAnnotation_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_PathAnnotation_Test.java
@@ -410,12 +410,12 @@ public class Remote_PathAnnotation_Test {
                assertEquals("{x:'1|2'}",x.getX1("1","2"));
                assertThrown(()->x.getX1()).contains("Minimum number of items 
not met");
                assertThrown(()->x.getX1("1","2","3")).contains("Maximum number 
of items exceeded");
-               assertEquals("{x:null}",x.getX1((String)null));
+               assertEquals("{x:'null'}",x.getX1((String)null));
                assertEquals("{x:'1'}",x.getX2(new String[]{"1"}));
                assertEquals("{x:'1|2'}",x.getX2(new String[]{"1","2"}));
                assertThrown(()->x.getX2(new String[]{})).contains("Minimum 
number of items not met");
                assertThrown(()->x.getX2(new 
String[]{"1","2","3"})).contains("Maximum number of items exceeded");
-               assertEquals("{x:null}",x.getX2(new String[]{null}));
+               assertEquals("{x:'null'}",x.getX2(new String[]{null}));
                assertEquals("{x:'1|1'}",x.getX3("1","1"));
                assertEquals("{x:'1|1'}",x.getX4(new String[]{"1","1"}));
                assertEquals("{x:'1|2'}",x.getX5("1","2"));
@@ -463,12 +463,12 @@ public class Remote_PathAnnotation_Test {
                assertThrown(()->x.getX3("bar")).contains("Value does not match 
one of the expected values.  Must be one of the following: ['foo']");
                assertEquals("{x:'foo'}",x.getX4("foo"));
                assertThrown(()->x.getX4("bar")).contains("Value does not match 
one of the expected values.  Must be one of the following: ['foo']");
-               assertEquals("{x:null}",x.getX4((String)null));
+               assertEquals("{x:'null'}",x.getX4((String)null));
                assertEquals("{x:'foo123'}",x.getX5("foo123"));
                assertThrown(()->x.getX5("bar")).contains("Value does not match 
expected pattern");
                assertEquals("{x:'foo123'}",x.getX6("foo123"));
                assertThrown(()->x.getX6("foo")).contains("Value does not match 
expected pattern");
-               assertEquals("{x:null}",x.getX6((String)null));
+               assertEquals("{x:'null'}",x.getX6((String)null));
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -586,7 +586,7 @@ public class Remote_PathAnnotation_Test {
        public static class K  {
                @RestOp(path="/*")
                public String get(RestRequest req) throws Exception {
-                       return req.getPathMatch().getRemainder();
+                       return req.getPathParams().getRemainder().orElse(null);
                }
        }
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/Paths_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/rest/Paths_Test.java
index 049b3d8..cf55322 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/Paths_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/Paths_Test.java
@@ -30,8 +30,8 @@ public class Paths_Test {
        static OMap getPaths(RestRequest req) {
                return OMap.create()
                        .a("pathInfo", req.getPathInfo())
-                       .a("pathRemainder", req.getPathMatch().getRemainder())
-                       .a("pathRemainderUndecoded", 
req.getPathMatch().getRemainderUndecoded())
+                       .a("pathRemainder", 
req.getPathParams().getRemainder().orElse(null))
+                       .a("pathRemainderUndecoded", 
req.getPathParams().getRemainderUndecoded().orElse(null))
                        .a("requestURI", req.getRequestURI())
                        .a("requestParentURI", 
req.getUriContext().getRootRelativePathInfoParent())
                        .a("requestURL", req.getRequestURL())
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestOp_Params_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestOp_Params_Test.java
index 5ea032c..8fcc5bb 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/RestOp_Params_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/RestOp_Params_Test.java
@@ -109,7 +109,7 @@ public class RestOp_Params_Test {
                        return t != null;
                }
                @RestGet
-               public boolean r(RequestPath t) {
+               public boolean r(RequestPathParams t) {
                        return t != null;
                }
                @RestGet
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Path_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Path_Test.java
index 13900cb..5e753f7 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Path_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Path_Test.java
@@ -20,8 +20,8 @@ import java.util.*;
 
 import org.apache.juneau.collections.*;
 import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
-import org.apache.juneau.marshall.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.RestResponse;
 import org.apache.juneau.rest.client.*;
@@ -383,31 +383,31 @@ public class Path_Test {
        @Rest(path="/f/{a}/{b}")
        public static class F  {
                @RestGet(path="/")
-               public String a(RequestPath path) {
-                       return format("a: {0}", path);
+               public String a(RequestPathParams path) {
+                       return format("a: {0}", path.toString(true));
                }
                @RestGet(path="/*")
-               public String b(RequestPath path) {
-                       return format("b: {0}", path);
+               public String b(RequestPathParams path) {
+                       return format("b: {0}", path.toString(true));
                }
                @RestGet(path="/fc")
-               public String c(RequestPath path) {
-                       return format("c: {0}", path);
+               public String c(RequestPathParams path) {
+                       return format("c: {0}", path.toString(true));
                }
                @RestGet(path="/fd/{c}/{d}")
-               public String d(RequestPath path) {
-                       return format("d: {0}", path);
+               public String d(RequestPathParams path) {
+                       return format("d: {0}", path.toString(true));
                }
                @RestGet(path="/fe/{a}/{b}")
-               public String e(RequestPath path) {
-                       return format("e: {0}", path);
+               public String e(RequestPathParams path) {
+                       return format("e: {0}", path.toString(true));
                }
                @RestGet(path="/ff/{c}/{d}/*")
-               public String f(RequestPath path) {
-                       return format("f: {0}", path);
+               public String f(RequestPathParams path) {
+                       return format("f: {0}", path.toString(true));
                }
                private String format(String msg, Object...args) {
-                       return SimpleJson.DEFAULT.format(msg, args);
+                       return StringUtils.format(msg, args);
                }
        }
 

Reply via email to