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