001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.rest; 014 015import static org.apache.juneau.internal.StringUtils.*; 016 017import java.lang.reflect.*; 018import java.util.*; 019 020import org.apache.juneau.*; 021import org.apache.juneau.collections.*; 022import org.apache.juneau.httppart.*; 023import org.apache.juneau.oapi.*; 024import org.apache.juneau.parser.*; 025import org.apache.juneau.http.exception.*; 026 027/** 028 * Contains information about the matched path on the HTTP request. 029 * 030 * <p> 031 * Provides access to the matched path variables and path match remainder. 032 * 033 * <ul class='seealso'> 034 * <li class='link'>{@doc RestmRequestPathMatch} 035 * </ul> 036 */ 037public class RequestPath extends TreeMap<String,String> { 038 private static final long serialVersionUID = 1L; 039 040 private final RestRequest req; 041 private HttpPartParserSession parser; 042 043 RequestPath(RestCall call) { 044 super(String.CASE_INSENSITIVE_ORDER); 045 this.req = call.getRestRequest(); 046 putAll(call.getPathVars()); 047 } 048 049 RequestPath parser(HttpPartParserSession parser) { 050 this.parser = parser; 051 return this; 052 } 053 054 RequestPath remainder(String remainder) { 055 put("/**", remainder); 056 put("/*", urlDecode(remainder)); 057 return this; 058 } 059 060 /** 061 * Sets a request query parameter value. 062 * 063 * @param name The parameter name. 064 * @param value The parameter value. 065 */ 066 public void put(String name, Object value) { 067 super.put(name, value.toString()); 068 } 069 070 /** 071 * Returns the specified path parameter converted to a String. 072 * 073 * @param name The path variable name. 074 * @return The parameter value. 075 * @throws BadRequest Thrown if input could not be parsed. 076 * @throws InternalServerError Thrown if any other exception occurs. 077 */ 078 public String getString(String name) throws BadRequest, InternalServerError { 079 return getInner(parser, null, name, null, req.getBeanSession().string()); 080 } 081 082 /** 083 * Returns the specified path parameter converted to an integer. 084 * 085 * @param name The path variable name. 086 * @return The parameter value. 087 * @throws BadRequest Thrown if input could not be parsed. 088 * @throws InternalServerError Thrown if any other exception occurs. 089 */ 090 public int getInt(String name) throws BadRequest, InternalServerError { 091 return getInner(parser, null, name, null, getClassMeta(int.class)); 092 } 093 094 /** 095 * Returns the specified path parameter converted to a boolean. 096 * 097 * @param name The path variable name. 098 * @return The parameter value. 099 * @throws BadRequest Thrown if input could not be parsed. 100 * @throws InternalServerError Thrown if any other exception occurs. 101 */ 102 public boolean getBoolean(String name) throws BadRequest, InternalServerError { 103 return getInner(null, null, name, null, getClassMeta(boolean.class)); 104 } 105 106 /** 107 * Returns the specified path parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 108 * 109 * <h5 class='section'>Examples:</h5> 110 * <p class='bcode w800'> 111 * <jc>// Parse into an integer.</jc> 112 * <jk>int</jk> myparam = path.get(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>); 113 * 114 * <jc>// Parse into an int array.</jc> 115 * <jk>int</jk>[] myparam = path.get(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>); 116 117 * <jc>// Parse into a bean.</jc> 118 * MyBean myparam = path.get(<js>"myparam"</js>, MyBean.<jk>class</jk>); 119 * 120 * <jc>// Parse into a linked-list of objects.</jc> 121 * List myparam = path.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>); 122 * 123 * <jc>// Parse into a map of object keys/values.</jc> 124 * Map myparam = path.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>); 125 * </p> 126 * 127 * <ul class='seealso'> 128 * <li class='jf'>{@link RestContext#REST_partParser} 129 * </ul> 130 * 131 * @param name The attribute name. 132 * @param type The class type to convert the attribute value to. 133 * @param <T> The class type to convert the attribute value to. 134 * @return The attribute value converted to the specified class type. 135 * @throws BadRequest Thrown if input could not be parsed. 136 * @throws InternalServerError Thrown if any other exception occurs. 137 */ 138 public <T> T get(String name, Class<T> type) throws BadRequest, InternalServerError { 139 return getInner(null, null, name, null, getClassMeta(type)); 140 } 141 142 /** 143 * Same as {@link #get(String, Class)} but allows you to override the part parser. 144 * 145 * @param parser 146 * The parser to use for parsing the string value. 147 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 148 * @param schema 149 * The schema object that defines the format of the input. 150 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 151 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 152 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 153 * @param name The attribute name. 154 * @param type The class type to convert the attribute value to. 155 * @param <T> The class type to convert the attribute value to. 156 * @return The attribute value converted to the specified class type. 157 * @throws BadRequest Thrown if input could not be parsed or fails schema validation. 158 * @throws InternalServerError Thrown if any other exception occurs. 159 */ 160 public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, String name, Class<T> type) throws BadRequest, InternalServerError { 161 return getInner(parser, schema, name, null, getClassMeta(type)); 162 } 163 164 /** 165 * Returns the specified query parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 166 * 167 * <p> 168 * Similar to {@link #get(String,Class)} but allows for complex collections of POJOs to be created. 169 * 170 * <p> 171 * Use this method if you want to parse into a parameterized <c>Map</c>/<c>Collection</c> object. 172 * 173 * <h5 class='section'>Examples:</h5> 174 * <p class='bcode w800'> 175 * <jc>// Parse into a linked-list of strings.</jc> 176 * List<String> myparam = req.getPathParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 177 * 178 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 179 * List<List<String>> myparam = req.getPathParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 180 * 181 * <jc>// Parse into a map of string keys/values.</jc> 182 * Map<String,String> myparam = req.getPathParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 183 * 184 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 185 * Map<String,List<MyBean>> myparam = req.getPathParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 186 * </p> 187 * 188 * <ul class='notes'> 189 * <li> 190 * <c>Collections</c> must be followed by zero or one parameter representing the value type. 191 * <li> 192 * <c>Maps</c> must be followed by zero or two parameters representing the key and value types. 193 * </ul> 194 * 195 * <ul class='seealso'> 196 * <li class='jf'>{@link RestContext#REST_partParser} 197 * </ul> 198 * 199 * @param name The attribute name. 200 * @param type 201 * The type of object to create. 202 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 203 * @param args 204 * The type arguments of the class if it's a collection or map. 205 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 206 * <br>Ignored if the main type is not a map or collection. 207 * @param <T> The class type to convert the attribute value to. 208 * @return The attribute value converted to the specified class type. 209 * @throws BadRequest Thrown if input could not be parsed. 210 * @throws InternalServerError Thrown if any other exception occurs. 211 */ 212 public <T> T get(String name, Type type, Type...args) throws BadRequest, InternalServerError { 213 return getInner(null, null, name, null, getClassMeta(type, args)); 214 } 215 216 /** 217 * Same as {@link #get(String, Type, Type...)} but allows you to override the part parser. 218 * 219 * @param parser 220 * The parser to use for parsing the string value. 221 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 222 * @param schema 223 * The schema object that defines the format of the input. 224 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 225 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 226 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 227 * @param name The attribute name. 228 * @param type 229 * The type of object to create. 230 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 231 * @param args 232 * The type arguments of the class if it's a collection or map. 233 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 234 * <br>Ignored if the main type is not a map or collection. 235 * @param <T> The class type to convert the attribute value to. 236 * @return The attribute value converted to the specified class type. 237 * @throws BadRequest Thrown if input could not be parsed or fails schema validation. 238 * @throws InternalServerError Thrown if any other exception occurs. 239 */ 240 public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, String name, Type type, Type...args) throws BadRequest, InternalServerError { 241 return getInner(parser, schema, name, null, getClassMeta(type, args)); 242 } 243 244 /* Workhorse method */ 245 private <T> T getInner(HttpPartParserSession parser, HttpPartSchema schema, String name, T def, ClassMeta<T> cm) throws BadRequest, InternalServerError { 246 if (parser == null) 247 parser = req.getPartParser(); 248 try { 249 if (cm.isMapOrBean() && isOneOf(name, "*", "")) { 250 OMap m = new OMap(); 251 for (Map.Entry<String,String> e : this.entrySet()) { 252 String k = e.getKey(); 253 HttpPartSchema pschema = schema == null ? null : schema.getProperty(k); 254 ClassMeta<?> cm2 = cm.getValueType(); 255 m.put(k, getInner(parser, pschema, k, null, cm2)); 256 } 257 return req.getBeanSession().convertToType(m, cm); 258 } 259 T t = parse(parser, schema, get(name), cm); 260 return (t == null ? def : t); 261 } catch (SchemaValidationException e) { 262 throw new BadRequest(e, "Validation failed on path parameter ''{0}''. ", name); 263 } catch (ParseException e) { 264 throw new BadRequest(e, "Could not parse path parameter ''{0}''.", name) ; 265 } catch (Exception e) { 266 throw new InternalServerError(e, "Could not parse path parameter ''{0}''.", name) ; 267 } 268 } 269 270 /* Workhorse method */ 271 private <T> T parse(HttpPartParserSession parser, HttpPartSchema schema, String val, ClassMeta<T> cm) throws SchemaValidationException, ParseException { 272 if (parser == null) 273 parser = this.parser; 274 return parser.parse(HttpPartType.PATH, schema, val, cm); 275 } 276 277 /** 278 * Returns the decoded remainder of the URL following any path pattern matches. 279 * 280 * <p> 281 * The behavior of path remainder is shown below given the path pattern "/foo/*": 282 * <table class='styled'> 283 * <tr> 284 * <th>URL</th> 285 * <th>Path Remainder</th> 286 * </tr> 287 * <tr> 288 * <td><c>/foo</c></td> 289 * <td><jk>null</jk></td> 290 * </tr> 291 * <tr> 292 * <td><c>/foo/</c></td> 293 * <td><js>""</js></td> 294 * </tr> 295 * <tr> 296 * <td><c>/foo//</c></td> 297 * <td><js>"/"</js></td> 298 * </tr> 299 * <tr> 300 * <td><c>/foo///</c></td> 301 * <td><js>"//"</js></td> 302 * </tr> 303 * <tr> 304 * <td><c>/foo/a/b</c></td> 305 * <td><js>"a/b"</js></td> 306 * </tr> 307 * <tr> 308 * <td><c>/foo//a/b/</c></td> 309 * <td><js>"/a/b/"</js></td> 310 * </tr> 311 * <tr> 312 * <td><c>/foo/a%2Fb</c></td> 313 * <td><js>"a/b"</js></td> 314 * </tr> 315 * </table> 316 * 317 * <h5 class='section'>Example:</h5> 318 * <p class='bcode w800'> 319 * <jc>// REST method</jc> 320 * <ja>@RestMethod</ja>(name=<jsf>GET</jsf>,path=<js>"/foo/{bar}/*"</js>) 321 * <jk>public</jk> String doGetById(RequestPathMatch path, <jk>int</jk> bar) { 322 * <jk>return</jk> path.getRemainder(); 323 * } 324 * </p> 325 * 326 * <p> 327 * The remainder can also be retrieved by calling <code>get(<js>"/*"</js>)</code>. 328 * 329 * @return The path remainder string. 330 */ 331 public String getRemainder() { 332 return get("/*"); 333 } 334 335 /** 336 * Same as {@link #getRemainder()} but doesn't decode characters. 337 * 338 * <p> 339 * The undecoded remainder can also be retrieved by calling <code>get(<js>"/**"</js>)</code>. 340 * 341 * @return The un-decoded path remainder. 342 */ 343 public String getRemainderUndecoded() { 344 return get("/**"); 345 } 346 347 //----------------------------------------------------------------------------------------------------------------- 348 // Helper methods 349 //----------------------------------------------------------------------------------------------------------------- 350 351 private <T> ClassMeta<T> getClassMeta(Type type, Type...args) { 352 return req.getBeanSession().getClassMeta(type, args); 353 } 354 355 private <T> ClassMeta<T> getClassMeta(Class<T> type) { 356 return req.getBeanSession().getClassMeta(type); 357 } 358}