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.parser.*; 023 024/** 025 * Contains information about the matched path on the HTTP request. 026 * 027 * <p> 028 * Provides access to the matched path variables and path match remainder. 029 * 030 * <h5 class='section'>See Also:</h5> 031 * <ul> 032 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-rest-server.RequestPathMatch">Overview > juneau-rest-server > RequestPathMatch</a> 033 * </ul> 034 */ 035@SuppressWarnings("unchecked") 036public class RequestPathMatch extends TreeMap<String,String> { 037 private static final long serialVersionUID = 1L; 038 039 private HttpPartParser parser; 040 private BeanSession beanSession; 041 private String remainder, pattern; 042 043 RequestPathMatch() { 044 super(String.CASE_INSENSITIVE_ORDER); 045 } 046 047 RequestPathMatch parser(HttpPartParser parser) { 048 this.parser = parser; 049 return this; 050 } 051 052 RequestPathMatch beanSession(BeanSession beanSession) { 053 this.beanSession = beanSession; 054 return this; 055 } 056 057 RequestPathMatch remainder(String remainder) { 058 this.remainder = remainder; 059 return this; 060 } 061 062 RequestPathMatch pattern(String pattern) { 063 this.pattern = pattern; 064 return this; 065 } 066 067 /** 068 * Sets a request query parameter value. 069 * 070 * @param name The parameter name. 071 * @param value The parameter value. 072 */ 073 public void put(String name, Object value) { 074 super.put(name, value.toString()); 075 } 076 077 /** 078 * Returns the specified path parameter converted to a String. 079 * 080 * @param name The path variable name. 081 * @return The parameter value. 082 * @throws ParseException 083 */ 084 public String getString(String name) throws ParseException { 085 return parse(parser, name, beanSession.string()); 086 } 087 088 /** 089 * Returns the specified path parameter converted to an integer. 090 * 091 * @param name The path variable name. 092 * @return The parameter value. 093 * @throws ParseException 094 */ 095 public int getInt(String name) throws ParseException { 096 return parse(parser, name, beanSession.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 ParseException 105 */ 106 public boolean getBoolean(String name) throws ParseException { 107 return parse(parser, name, beanSession.getClassMeta(boolean.class)); 108 } 109 110 /** 111 * Returns the specified path parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 112 * 113 * <h5 class='section'>Examples:</h5> 114 * <p class='bcode'> 115 * <jc>// Parse into an integer.</jc> 116 * <jk>int</jk> myparam = path.get(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>); 117 * 118 * <jc>// Parse into an int array.</jc> 119 * <jk>int</jk>[] myparam = path.get(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>); 120 121 * <jc>// Parse into a bean.</jc> 122 * MyBean myparam = path.get(<js>"myparam"</js>, MyBean.<jk>class</jk>); 123 * 124 * <jc>// Parse into a linked-list of objects.</jc> 125 * List myparam = path.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>); 126 * 127 * <jc>// Parse into a map of object keys/values.</jc> 128 * Map myparam = path.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>); 129 * </p> 130 * 131 * <h5 class='section'>See Also:</h5> 132 * <ul> 133 * <li class='jf'>{@link RestContext#REST_partParser} 134 * </ul> 135 * 136 * @param name The attribute name. 137 * @param type The class type to convert the attribute value to. 138 * @param <T> The class type to convert the attribute value to. 139 * @return The attribute value converted to the specified class type. 140 * @throws ParseException 141 */ 142 public <T> T get(String name, Class<T> type) throws ParseException { 143 return get(parser, name, type); 144 } 145 146 /** 147 * Same as {@link #get(String, Class)} but allows you to override the part parser. 148 * 149 * @param parser 150 * The parser to use for parsing the string value. 151 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 152 * @param name The attribute name. 153 * @param type The class type to convert the attribute value to. 154 * @param <T> The class type to convert the attribute value to. 155 * @return The attribute value converted to the specified class type. 156 * @throws ParseException 157 */ 158 public <T> T get(HttpPartParser parser, String name, Class<T> type) throws ParseException { 159 return parse(parser, name, beanSession.getClassMeta(type)); 160 } 161 162 /** 163 * Returns the specified query parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 164 * 165 * <p> 166 * Similar to {@link #get(String,Class)} but allows for complex collections of POJOs to be created. 167 * 168 * <p> 169 * Use this method if you want to parse into a parameterized <code>Map</code>/<code>Collection</code> object. 170 * 171 * <h5 class='section'>Examples:</h5> 172 * <p class='bcode'> 173 * <jc>// Parse into a linked-list of strings.</jc> 174 * List<String> myparam = req.getPathParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 175 * 176 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 177 * List<List<String>> myparam = req.getPathParameter(<js>"myparam"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 178 * 179 * <jc>// Parse into a map of string keys/values.</jc> 180 * Map<String,String> myparam = req.getPathParameter(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 181 * 182 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 183 * 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>); 184 * </p> 185 * 186 * <h5 class='section'>Notes:</h5> 187 * <ul class='spaced-list'> 188 * <li> 189 * <code>Collections</code> must be followed by zero or one parameter representing the value type. 190 * <li> 191 * <code>Maps</code> must be followed by zero or two parameters representing the key and value types. 192 * </ul> 193 * 194 * <h5 class='section'>See Also:</h5> 195 * <ul> 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 ParseException 210 */ 211 public <T> T get(String name, Type type, Type...args) throws ParseException { 212 return get(parser, name, type, args); 213 } 214 215 /** 216 * Same as {@link #get(String, Type, Type...)} but allows you to override the part parser. 217 * 218 * @param parser 219 * The parser to use for parsing the string value. 220 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 221 * @param name The attribute name. 222 * @param type 223 * The type of object to create. 224 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 225 * @param args 226 * The type arguments of the class if it's a collection or map. 227 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 228 * <br>Ignored if the main type is not a map or collection. 229 * @param <T> The class type to convert the attribute value to. 230 * @return The attribute value converted to the specified class type. 231 * @throws ParseException 232 */ 233 public <T> T get(HttpPartParser parser, String name, Type type, Type...args) throws ParseException { 234 return (T)parse(parser, name, beanSession.getClassMeta(type, args)); 235 } 236 237 238 /* Workhorse method */ 239 <T> T parse(HttpPartParser parser, String name, ClassMeta<T> cm) throws ParseException { 240 if (parser == null) 241 parser = this.parser; 242 Object attr = get(name); 243 T t = null; 244 if (attr != null) 245 t = parser.parse(HttpPartType.PATH, attr.toString(), cm); 246 if (t == null && cm.isPrimitive()) 247 return cm.getPrimitiveDefault(); 248 return t; 249 } 250 251 /** 252 * Returns the decoded remainder of the URL following any path pattern matches. 253 * 254 * <p> 255 * The behavior of path remainder is shown below given the path pattern "/foo/*": 256 * <table class='styled'> 257 * <tr> 258 * <th>URL</th> 259 * <th>Path Remainder</th> 260 * </tr> 261 * <tr> 262 * <td><code>/foo</code></td> 263 * <td><jk>null</jk></td> 264 * </tr> 265 * <tr> 266 * <td><code>/foo/</code></td> 267 * <td><js>""</js></td> 268 * </tr> 269 * <tr> 270 * <td><code>/foo//</code></td> 271 * <td><js>"/"</js></td> 272 * </tr> 273 * <tr> 274 * <td><code>/foo///</code></td> 275 * <td><js>"//"</js></td> 276 * </tr> 277 * <tr> 278 * <td><code>/foo/a/b</code></td> 279 * <td><js>"a/b"</js></td> 280 * </tr> 281 * <tr> 282 * <td><code>/foo//a/b/</code></td> 283 * <td><js>"/a/b/"</js></td> 284 * </tr> 285 * <tr> 286 * <td><code>/foo/a%2Fb</code></td> 287 * <td><js>"a/b"</js></td> 288 * </tr> 289 * </table> 290 * 291 * <h5 class='section'>Example:</h5> 292 * <p class='bcode'> 293 * <jc>// REST method</jc> 294 * <ja>@RestMethod</ja>(name=<jsf>GET</jsf>,path=<js>"/foo/{bar}/*"</js>) 295 * <jk>public</jk> String doGetById(RequestPathMatch path, <jk>int</jk> bar) { 296 * <jk>return</jk> path.getRemainder(); 297 * } 298 * </p> 299 * 300 * @return The path remainder string. 301 */ 302 public String getRemainder() { 303 return urlDecode(remainder); 304 } 305 306 /** 307 * Same as {@link #getRemainder()} but doesn't decode characters. 308 * 309 * @return The un-decoded path remainder. 310 */ 311 public String getRemainderUndecoded() { 312 return remainder; 313 } 314 315 /** 316 * Returns the path pattern that matched this request. 317 * 318 * @return The path pattern that matched this request. 319 */ 320 public String getPattern() { 321 return pattern; 322 } 323}