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&lt;String&gt; 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&lt;List&lt;String&gt;&gt; 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&lt;String,String&gt; 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&lt;String,List&lt;MyBean&gt;&gt; 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}