org.apache.juneau.rest.RequestPath Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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.httppart.*;
import org.apache.juneau.oapi.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.rest.exception.*;
/**
* Contains information about the matched path on the HTTP request.
*
*
* Provides access to the matched path variables and path match remainder.
*
*
See Also:
*
* - {@doc juneau-rest-server.RestMethod.RequestPathMatch}
*
*/
public class RequestPath extends TreeMap {
private static final long serialVersionUID = 1L;
private final RestRequest req;
private HttpPartParser parser;
private String pattern;
RequestPath(RestRequest req) {
super(String.CASE_INSENSITIVE_ORDER);
this.req = req;
}
RequestPath parser(HttpPartParser parser) {
this.parser = parser;
return this;
}
RequestPath remainder(String remainder) {
put("/**", remainder);
put("/*", urlDecode(remainder));
return this;
}
RequestPath pattern(String pattern) {
this.pattern = pattern;
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.
*
* Examples:
*
* // Parse into an integer.
* int myparam = path.get("myparam" , int .class );
*
* // Parse into an int array.
* int [] myparam = path.get("myparam" , int [].class );
* // Parse into a bean.
* MyBean myparam = path.get("myparam" , MyBean.class );
*
* // Parse into a linked-list of objects.
* List myparam = path.get("myparam" , LinkedList.class );
*
* // Parse into a map of object keys/values.
* Map myparam = path.get("myparam" , TreeMap.class );
*
*
* See Also:
*
* - {@link RestContext#REST_partParser}
*
*
* @param name The attribute name.
* @param type The class type to convert the attribute value to.
* @param 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 get(String name, Class type) throws BadRequest, InternalServerError {
return getInner(null, null, name, null, this.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.
*
If null , uses the part parser defined on the resource/method.
* @param schema
* The schema object that defines the format of the input.
*
If null , defaults to the schema defined on the parser.
*
If that's also null , defaults to {@link HttpPartSchema#DEFAULT}.
*
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 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 get(HttpPartParser parser, HttpPartSchema schema, String name, Class type) throws BadRequest, InternalServerError {
return getInner(parser, schema, name, null, this.getClassMeta(type));
}
/**
* Returns the specified query parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource.
*
*
* Similar to {@link #get(String,Class)} but allows for complex collections of POJOs to be created.
*
*
* Use this method if you want to parse into a parameterized Map
/Collection
object.
*
*
Examples:
*
* // Parse into a linked-list of strings.
* List<String> myparam = req.getPathParameter("myparam" , LinkedList.class , String.class );
*
* // Parse into a linked-list of linked-lists of strings.
* List<List<String>> myparam = req.getPathParameter("myparam" , LinkedList.class , LinkedList.class , String.class );
*
* // Parse into a map of string keys/values.
* Map<String,String> myparam = req.getPathParameter("myparam" , TreeMap.class , String.class , String.class );
*
* // Parse into a map containing string keys and values of lists containing beans.
* Map<String,List<MyBean>> myparam = req.getPathParameter("myparam" , TreeMap.class , String.class , List.class , MyBean.class );
*
*
* Notes:
*
* -
*
Collections
must be followed by zero or one parameter representing the value type.
* -
*
Maps
must be followed by zero or two parameters representing the key and value types.
*
*
* See Also:
*
* - {@link RestContext#REST_partParser}
*
*
* @param name The attribute name.
* @param type
* The type of object to create.
*
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.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
*
Ignored if the main type is not a map or collection.
* @param 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 get(String name, Type type, Type...args) throws BadRequest, InternalServerError {
return getInner(null, null, name, null, this.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.
*
If null , uses the part parser defined on the resource/method.
* @param schema
* The schema object that defines the format of the input.
*
If null , defaults to the schema defined on the parser.
*
If that's also null , defaults to {@link HttpPartSchema#DEFAULT}.
*
Only used if parser is schema-aware (e.g. {@link OpenApiParser}).
* @param name The attribute name.
* @param type
* The type of object to create.
*
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.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
*
Ignored if the main type is not a map or collection.
* @param 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 get(HttpPartParser parser, HttpPartSchema schema, String name, Type type, Type...args) throws BadRequest, InternalServerError {
return getInner(parser, schema, name, null, this.getClassMeta(type, args));
}
/* Workhorse method */
private T getInner(HttpPartParser parser, HttpPartSchema schema, String name, T def, ClassMeta cm) throws BadRequest, InternalServerError {
try {
if (cm.isMapOrBean() && isOneOf(name, "*", "")) {
ObjectMap m = new ObjectMap();
for (Map.Entry 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 parse(HttpPartParser parser, HttpPartSchema schema, String val, ClassMeta cm) throws SchemaValidationException, ParseException {
if (parser == null)
parser = this.parser;
return parser.createPartSession(req.getParserSessionArgs()).parse(HttpPartType.PATH, schema, val, cm);
}
/**
* Returns the decoded remainder of the URL following any path pattern matches.
*
*
* The behavior of path remainder is shown below given the path pattern "/foo/*":
*
*
* URL
* Path Remainder
*
*
* /foo
* null
*
*
* /foo/
* ""
*
*
* /foo//
* "/"
*
*
* /foo///
* "//"
*
*
* /foo/a/b
* "a/b"
*
*
* /foo//a/b/
* "/a/b/"
*
*
* /foo/a%2Fb
* "a/b"
*
*
*
* Example:
*
* // REST method
* @RestMethod (name=GET ,path="/foo/{bar}/*" )
* public String doGetById(RequestPathMatch path, int bar) {
* return path.getRemainder();
* }
*
*
*
* The remainder can also be retrieved by calling get("/*" )
.
*
* @return The path remainder string.
*/
public String getRemainder() {
return get("/*");
}
/**
* Same as {@link #getRemainder()} but doesn't decode characters.
*
*
* The undecoded remainder can also be retrieved by calling get("/**" )
.
*
* @return The un-decoded path remainder.
*/
public String getRemainderUndecoded() {
return get("/**");
}
/**
* Returns the path pattern that matched this request.
*
* @return The path pattern that matched this request.
*/
public String getPattern() {
return pattern;
}
//-----------------------------------------------------------------------------------------------------------------
// Helper methods
//-----------------------------------------------------------------------------------------------------------------
private ClassMeta getClassMeta(Type type, Type...args) {
return req.getBeanSession().getClassMeta(type, args);
}
private ClassMeta getClassMeta(Class type) {
return req.getBeanSession().getClassMeta(type);
}
}