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