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.ArrayUtils.*;
016import static org.apache.juneau.internal.StringUtils.*;
017
018import java.lang.reflect.*;
019import java.util.*;
020
021import javax.servlet.http.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.httppart.*;
025import org.apache.juneau.internal.*;
026import org.apache.juneau.json.*;
027import org.apache.juneau.oapi.*;
028import org.apache.juneau.parser.*;
029import org.apache.juneau.http.exception.*;
030import org.apache.juneau.utils.*;
031
032/**
033 * Represents the query parameters in an HTTP request.
034 *
035 * <p>
036 * Similar in functionality to the {@link HttpServletRequest#getParameter(String)} except only looks in the URL string, not parameters from
037 * URL-Encoded FORM posts.
038 * <br>This can be useful in cases where you're using GET parameters on FORM POSTs, and you don't want the body of the request to be read.
039 *
040 * <ul class='seealso'>
041 *    <li class='link'>{@doc juneau-rest-server.RestMethod.RequestQuery}
042 * </ul>
043 */
044@SuppressWarnings("unchecked")
045public final class RequestQuery extends LinkedHashMap<String,String[]> {
046   private static final long serialVersionUID = 1L;
047
048   private final RestRequest req;
049   private HttpPartParser parser;
050
051   RequestQuery(RestRequest req) {
052      this.req = req;
053   }
054
055   RequestQuery parser(HttpPartParser parser) {
056      this.parser = parser;
057      return this;
058   }
059
060   /*
061    * Create a copy of the request query parameters.
062    */
063   RequestQuery copy() {
064      RequestQuery rq = new RequestQuery(req);
065      rq.putAll(this);
066      return rq;
067   }
068
069   /**
070    * Adds default entries to these query parameters.
071    *
072    * <p>
073    * This includes the default queries defined at the resource and method levels.
074    *
075    * @param defaultEntries
076    *    The default entries.
077    *    <br>Can be <jk>null</jk>.
078    * @return This object (for method chaining).
079    */
080   public RequestQuery addDefault(Map<String,Object> defaultEntries) {
081      if (defaultEntries != null) {
082         for (Map.Entry<String,Object> e : defaultEntries.entrySet()) {
083            String key = e.getKey();
084            Object value = e.getValue();
085            String[] v = get(key);
086            if (v == null || v.length == 0 || StringUtils.isEmpty(v[0]))
087               put(key, stringifyAll(value));
088         }
089      }
090      return this;
091   }
092
093   /**
094    * Adds a default entries to these query parameters.
095    *
096    * <p>
097    * Similar to {@link #put(String, Object)} but doesn't override existing values.
098    *
099    * @param name
100    *    The query parameter name.
101    * @param value
102    *    The query parameter value.
103    *    <br>Converted to a String using <c>toString()</c>.
104    *    <br>Ignored if value is <jk>null</jk> or blank.
105    * @return This object (for method chaining).
106    */
107   public RequestQuery addDefault(String name, Object value) {
108      return addDefault(Collections.singletonMap(name, value));
109   }
110
111   /**
112    * Same as {@link #get(Object)} but allows you to find the query parameter using a case-insensitive match.
113    *
114    * @param name The query parameter name.
115    * @param caseInsensitive If <jk>true</jk> use case-insensitive matching on the query parameter name.
116    * @return The resolved entry, or <jk>null</jk> if not found.
117    */
118   public String[] get(String name, boolean caseInsensitive) {
119      if (! caseInsensitive)
120         return get(name);
121      for (Map.Entry<String,String[]> e : entrySet())
122         if (e.getKey().equalsIgnoreCase(name))
123            return e.getValue();
124      return null;
125   }
126
127   /**
128    * Sets a request query parameter value.
129    *
130    * <p>
131    * This overwrites any existing value.
132    *
133    * @param name The parameter name.
134    * @param value
135    *    The parameter value.
136    *    <br>Can be <jk>null</jk>.
137    */
138   public void put(String name, Object value) {
139      if (value == null)
140         put(name, null);
141      else
142         put(name, stringifyAll(value));
143   }
144
145   /**
146    * Returns a query parameter value as a string.
147    *
148    * <p>
149    * If multiple query parameters have the same name, this returns only the first instance.
150    *
151    * @param name The URL parameter name.
152    * @return
153    *    The parameter value, or <jk>null</jk> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
154    */
155   public String getString(String name) {
156      return getString(name, false);
157   }
158
159   /**
160    * Same as {@link #getString(String)} but allows you to search for the query parameter using case-insensitive matching.
161    *
162    * <p>
163    * If multiple query parameters have the same name, this returns only the first instance.
164    *
165    * @param name The URL parameter name.
166    * @param caseInsensitive If <jk>true</jk> use case insensitive matching on the query parameter name.
167    * @return
168    *    The parameter value, or <jk>null</jk> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
169    */
170   public String getString(String name, boolean caseInsensitive) {
171      String[] v = get(name, caseInsensitive);
172      if (v == null || v.length == 0)
173         return null;
174
175      // Fix for behavior difference between Tomcat and WAS.
176      // getParameter("foo") on "&foo" in Tomcat returns "".
177      // getParameter("foo") on "&foo" in WAS returns null.
178      if (v.length == 1 && v[0] == null)
179         return "";
180
181      return v[0];
182   }
183
184   /**
185    * Same as {@link #getString(String)} but returns the specified default value if the query parameter was not
186    * specified.
187    *
188    * @param name The URL parameter name.
189    * @param def The default value.
190    * @return
191    *    The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
192    */
193   public String getString(String name, String def) {
194      String s = getString(name);
195      return StringUtils.isEmpty(s) ? def : s;
196   }
197
198   /**
199    * Same as {@link #getString(String)} but converts the value to an integer.
200    *
201    * @param name The URL parameter name.
202    * @return
203    *    The parameter value, or <c>0</c> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
204    */
205   public int getInt(String name) {
206      return getInt(name, 0);
207   }
208
209   /**
210    * Same as {@link #getString(String,String)} but converts the value to an integer.
211    *
212    * @param name The URL parameter name.
213    * @param def The default value.
214    * @return
215    *    The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
216    */
217   public int getInt(String name, int def) {
218      String s = getString(name);
219      return StringUtils.isEmpty(s) ? def : Integer.parseInt(s);
220   }
221
222   /**
223    * Same as {@link #getString(String)} but converts the value to a boolean.
224    *
225    * @param name The URL parameter name.
226    * @return
227    *    The parameter value, or <jk>false</jk> if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
228    */
229   public boolean getBoolean(String name) {
230      return getBoolean(name, false);
231   }
232
233   /**
234    * Same as {@link #getString(String,String)} but converts the value to a boolean.
235    *
236    * @param name The URL parameter name.
237    * @param def The default value.
238    * @return
239    *    The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&amp;foo"</js>).
240    */
241   public boolean getBoolean(String name, boolean def) {
242      String s = getString(name);
243      return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s);
244   }
245
246   /**
247    * Returns the specified query parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource.
248    *
249    * <h5 class='section'>Examples:</h5>
250    * <p class='bcode w800'>
251    *    <jc>// Parse into an integer.</jc>
252    *    <jk>int</jk> myparam = query.get(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>);
253    *
254    *    <jc>// Parse into an int array.</jc>
255    *    <jk>int</jk>[] myparam = query.get(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>);
256
257    *    <jc>// Parse into a bean.</jc>
258    *    MyBean myparam = query.get(<js>"myparam"</js>, MyBean.<jk>class</jk>);
259    *
260    *    <jc>// Parse into a linked-list of objects.</jc>
261    *    List myparam = query.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>);
262    *
263    *    <jc>// Parse into a map of object keys/values.</jc>
264    *    Map myparam = query.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>);
265    * </p>
266    *
267    * <ul class='seealso'>
268    *    <li class='jf'>{@link RestContext#REST_partParser}
269    * </ul>
270    *
271    * @param name The parameter name.
272    * @param type The class type to convert the parameter value to.
273    * @param <T> The class type to convert the parameter value to.
274    * @return The parameter value converted to the specified class type.
275    * @throws BadRequest Thrown if input could not be parsed.
276    * @throws InternalServerError Thrown if any other exception occurs.
277    */
278   public <T> T get(String name, Class<T> type) throws BadRequest, InternalServerError {
279      return getInner(null, null, name, null, getClassMeta(type));
280   }
281
282   /**
283    * Same as {@link #get(String, Class)} but allows you to override the part parser.
284    *
285    * @param parser
286    *    The parser to use for parsing the string value.
287    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method.
288    * @param schema
289    *    The schema object that defines the format of the input.
290    *    <br>If <jk>null</jk>, defaults to the schema defined on the parser.
291    *    <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
292    *    <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}).
293    * @param name The parameter name.
294    * @param type The class type to convert the parameter value to.
295    * @param <T> The class type to convert the parameter value to.
296    * @return The parameter value converted to the specified class type.
297    * @throws BadRequest Thrown if input could not be parsed or fails schema validation.
298    * @throws InternalServerError Thrown if any other exception occurs.
299    */
300   public <T> T get(HttpPartParser parser, HttpPartSchema schema, String name, Class<T> type) throws BadRequest, InternalServerError {
301      return getInner(parser, schema, name, null, getClassMeta(type));
302   }
303
304   /**
305    * Same as {@link #get(String, Class)} except returns a default value if not found.
306    *
307    * @param name The parameter name.
308    * @param def The default value if the parameter was not specified or is <jk>null</jk>.
309    * @param type The class type to convert the parameter value to.
310    * @param <T> The class type to convert the parameter value to.
311    * @return The parameter value converted to the specified class type.
312    * @throws BadRequest Thrown if input could not be parsed.
313    * @throws InternalServerError Thrown if any other exception occurs.
314    */
315   public <T> T get(String name, T def, Class<T> type) throws BadRequest, InternalServerError {
316      return getInner(null, null, name, def, getClassMeta(type));
317   }
318
319   /**
320    * Same as {@link #get(String, Object, Class)} but allows you to override the part parser.
321    *
322    * @param parser
323    *    The parser to use for parsing the string value.
324    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method.
325    * @param schema
326    *    The schema object that defines the format of the input.
327    *    <br>If <jk>null</jk>, defaults to the schema defined on the parser.
328    *    <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
329    *    <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}).
330    * @param name The parameter name.
331    * @param def The default value if the parameter was not specified or is <jk>null</jk>.
332    * @param type The class type to convert the parameter value to.
333    * @param <T> The class type to convert the parameter value to.
334    * @return The parameter value converted to the specified class type.
335    * @throws BadRequest Thrown if input could not be parsed or fails schema validation.
336    * @throws InternalServerError Thrown if any other exception occurs.
337    */
338   public <T> T get(HttpPartParser parser, HttpPartSchema schema, String name, T def, Class<T> type) throws BadRequest, InternalServerError {
339      return getInner(parser, schema, name, def, getClassMeta(type));
340   }
341
342   /**
343    * Returns the specified query parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource.
344    *
345    * <p>
346    * Similar to {@link #get(String,Class)} but allows for complex collections of POJOs to be created.
347    *
348    * <h5 class='section'>Examples:</h5>
349    * <p class='bcode w800'>
350    *    <jc>// Parse into a linked-list of strings.</jc>
351    *    List&lt;String&gt; myparam = query.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
352    *
353    *    <jc>// Parse into a linked-list of linked-lists of strings.</jc>
354    *    List&lt;List&lt;String&gt;&gt; myparam = query.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
355    *
356    *    <jc>// Parse into a map of string keys/values.</jc>
357    *    Map&lt;String,String&gt; myparam = query.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>);
358    *
359    *    <jc>// Parse into a map containing string keys and values of lists containing beans.</jc>
360    *    Map&lt;String,List&lt;MyBean&gt;&gt; myparam = query.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);
361    * </p>
362    *
363    * <ul class='notes'>
364    *    <li>
365    *       <c>Collections</c> must be followed by zero or one parameter representing the value type.
366    *    <li>
367    *       <c>Maps</c> must be followed by zero or two parameters representing the key and value types.
368    * </ul>
369    *
370    * <ul class='seealso'>
371    *    <li class='jf'>{@link RestContext#REST_partParser}
372    * </ul>
373    *
374    * @param name The parameter name.
375    * @param type
376    *    The type of object to create.
377    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
378    * @param args
379    *    The type arguments of the class if it's a collection or map.
380    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
381    *    <br>Ignored if the main type is not a map or collection.
382    * @param <T> The class type to convert the parameter value to.
383    * @return The parameter value converted to the specified class type.
384    * @throws BadRequest Thrown if input could not be parsed.
385    * @throws InternalServerError Thrown if any other exception occurs.
386    */
387   public <T> T get(String name, Type type, Type...args) throws BadRequest, InternalServerError {
388      return getInner(null, null, name, null, (ClassMeta<T>)getClassMeta(type, args));
389   }
390
391   /**
392    * Same as {@link #get(String, Type, Type...)} but allows you to override the part parser.
393    *
394    * @param parser
395    *    The parser to use for parsing the string value.
396    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method.
397    * @param schema
398    *    The schema object that defines the format of the input.
399    *    <br>If <jk>null</jk>, defaults to the schema defined on the parser.
400    *    <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
401    *    <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}).
402    * @param name The parameter name.
403    * @param type
404    *    The type of object to create.
405    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
406    * @param args
407    *    The type arguments of the class if it's a collection or map.
408    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
409    *    <br>Ignored if the main type is not a map or collection.
410    * @param <T> The class type to convert the parameter value to.
411    * @return The parameter value converted to the specified class type.
412    * @throws BadRequest Thrown if input could not be parsed or fails schema validation.
413    * @throws InternalServerError Thrown if any other exception occurs.
414    */
415   public <T> T get(HttpPartParser parser, HttpPartSchema schema, String name, Type type, Type...args) throws BadRequest, InternalServerError {
416      return getInner(parser, schema, name, null, (ClassMeta<T>)getClassMeta(type, args));
417   }
418
419   /**
420    * Same as {@link #get(String, Class)} except returns a default value if not found.
421    *
422    * @param name The parameter name.
423    * @param type
424    *    The type of object to create.
425    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
426    * @param args
427    *    The type arguments of the class if it's a collection or map.
428    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
429    *    <br>Ignored if the main type is not a map or collection.
430    * @param def The default value if the parameter was not specified or is <jk>null</jk>.
431    * @param <T> The class type to convert the parameter value to.
432    * @return The parameter value converted to the specified class type.
433    * @throws BadRequest Thrown if input could not be parsed.
434    * @throws InternalServerError Thrown if any other exception occurs.
435    */
436   public <T> T get(String name, T def, Type type, Type...args) throws BadRequest, InternalServerError {
437      return getInner(null, null, name, def, (ClassMeta<T>)getClassMeta(type, args));
438   }
439
440   /**
441    * Same as {@link #get(String, Object, Type, Type...)} but allows you to override the part parser.
442    *
443    * @param parser
444    *    The parser to use for parsing the string value.
445    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method.
446    * @param schema
447    *    The schema object that defines the format of the input.
448    *    <br>If <jk>null</jk>, defaults to the schema defined on the parser.
449    *    <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
450    *    <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}).
451    * @param name The parameter name.
452    * @param type
453    *    The type of object to create.
454    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
455    * @param args
456    *    The type arguments of the class if it's a collection or map.
457    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
458    *    <br>Ignored if the main type is not a map or collection.
459    * @param def The default value if the parameter was not specified or is <jk>null</jk>.
460    * @param <T> The class type to convert the parameter value to.
461    * @return The parameter value converted to the specified class type.
462    * @throws BadRequest Thrown if input could not be parsed or fails schema validation.
463    * @throws InternalServerError Thrown if any other exception occurs.
464    */
465   public <T> T get(HttpPartParser parser, HttpPartSchema schema, String name, T def, Type type, Type...args) throws BadRequest, InternalServerError {
466      return getInner(parser, schema, name, def, (ClassMeta<T>)getClassMeta(type, args));
467   }
468
469   /**
470    * Same as {@link #get(String, Class)} except for use on multi-part parameters
471    * (e.g. <js>"&amp;key=1&amp;key=2&amp;key=3"</js> instead of <js>"&amp;key=@(1,2,3)"</js>).
472    *
473    * <p>
474    * This method must only be called when parsing into classes of type Collection or array.
475    *
476    * @param name The query parameter name.
477    * @param c The class type to convert the parameter value to.
478    * @param <T> The class type to convert the parameter value to.
479    * @return The query parameter value converted to the specified class type.
480    * @throws BadRequest Thrown if input could not be parsed.
481    * @throws InternalServerError Thrown if any other exception occurs.
482    */
483   public <T> T getAll(String name, Class<T> c) throws BadRequest, InternalServerError {
484      return getAllInner(null, null, name, null, getClassMeta(c));
485   }
486
487   /**
488    * Same as {@link #get(String, Type, Type...)} except for use on multi-part parameters
489    * (e.g. <js>"&amp;key=1&amp;key=2&amp;key=3"</js> instead of <js>"&amp;key=@(1,2,3)"</js>).
490    *
491    * <p>
492    * This method must only be called when parsing into classes of type Collection or array.
493    *
494    * @param name The query parameter name.
495    * @param type
496    *    The type of object to create.
497    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
498    * @param args
499    *    The type arguments of the class if it's a collection or map.
500    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
501    *    <br>Ignored if the main type is not a map or collection.
502    * @param <T> The class type to convert the parameter value to.
503    * @return The query parameter value converted to the specified class type.
504    * @throws BadRequest Thrown if input could not be parsed.
505    * @throws InternalServerError Thrown if any other exception occurs.
506    */
507   public <T> T getAll(String name, Type type, Type...args) throws BadRequest, InternalServerError {
508      return getAllInner(null, null, name, null, (ClassMeta<T>)getClassMeta(type, args));
509   }
510
511   /**
512    * Same as {@link #getAll(String, Type, Type...)} but allows you to override the part parser.
513    *
514    * @param parser
515    *    The parser to use for parsing the string value.
516    *    <br>If <jk>null</jk>, uses the part parser defined on the resource/method.
517    * @param schema
518    *    The schema object that defines the format of the input.
519    *    <br>If <jk>null</jk>, defaults to the schema defined on the parser.
520    *    <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
521    *    <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}).
522    * @param name The query parameter name.
523    * @param type
524    *    The type of object to create.
525    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
526    * @param args
527    *    The type arguments of the class if it's a collection or map.
528    *    <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
529    *    <br>Ignored if the main type is not a map or collection.
530    * @param <T> The class type to convert the parameter value to.
531    * @return The query parameter value converted to the specified class type.
532    * @throws BadRequest Thrown if input could not be parsed or fails schema validation.
533    * @throws InternalServerError Thrown if any other exception occurs.
534    */
535   public <T> T getAll(HttpPartParser parser, HttpPartSchema schema, String name, Type type, Type...args) throws BadRequest, InternalServerError {
536      return getAllInner(parser, schema, name, null, (ClassMeta<T>)getClassMeta(type, args));
537   }
538
539   /**
540    * Returns <jk>true</jk> if the request contains any of the specified query parameters.
541    *
542    * @param params The list of parameters to check for.
543    * @return <jk>true</jk> if the request contains any of the specified query parameters.
544    */
545   public boolean containsAnyKeys(String...params) {
546      for (String p : params)
547         if (containsKey(p))
548            return true;
549      return false;
550   }
551
552   /**
553    * Locates the special search query arguments in the query and returns them as a {@link SearchArgs} object.
554    *
555    * <p>
556    * The query arguments are as follows:
557    * <ul class='spaced-list'>
558    *    <li>
559    *       <js>"&amp;s="</js> - A comma-delimited list of column-name/search-token pairs.
560    *       <br>Example: <js>"&amp;s=column1=foo*,column2=*bar"</js>
561    *    <li>
562    *       <js>"&amp;v="</js> - A comma-delimited list column names to view.
563    *       <br>Example: <js>"&amp;v=column1,column2"</js>
564    *    <li>
565    *       <js>"&amp;o="</js> - A comma-delimited list column names to sort by.
566    *       <br>Column names can be suffixed with <js>'-'</js> to indicate descending order.
567    *       <br>Example: <js>"&amp;o=column1,column2-"</js>
568    *    <li>
569    *       <js>"&amp;p="</js> - The zero-index row number of the first row to display.
570    *       <br>Example: <js>"&amp;p=100"</js>
571    *    <li>
572    *       <js>"&amp;l="</js> - The number of rows to return.
573    *       <br><c>0</c> implies return all rows.
574    *       <br>Example: <js>"&amp;l=100"</js>
575    *    <li>
576    *       <js>"&amp;i="</js> - The case-insensitive search flag.
577    *       <br>Example: <js>"&amp;i=true"</js>
578    * </ul>
579    *
580    * <ul class='notes'>
581    *    <li>
582    *       Whitespace is trimmed in the parameters.
583    * </ul>
584    *
585    * @return
586    *    A new {@link SearchArgs} object initialized with the special search query arguments.
587    *    <br>Returns <jk>null</jk> if no search arguments were found.
588    */
589   public SearchArgs getSearchArgs() {
590      if (hasAny("s","v","o","p","l","i")) {
591         return new SearchArgs.Builder()
592            .search(getString("s"))
593            .view(getString("v"))
594            .sort(getString("o"))
595            .position(getInt("p"))
596            .limit(getInt("l"))
597            .ignoreCase(getBoolean("i"))
598            .build();
599      }
600      return null;
601   }
602
603   /**
604    * Returns <jk>true</jk> if the query parameters contains any of the specified names.
605    *
606    * @param paramNames The parameter names to check for.
607    * @return <jk>true</jk> if the query parameters contains any of the specified names.
608    */
609   public boolean hasAny(String...paramNames) {
610      for (String p : paramNames)
611         if (containsKey(p))
612            return true;
613      return false;
614   }
615
616   /* Workhorse method */
617   private <T> T getInner(HttpPartParser parser, HttpPartSchema schema, String name, T def, ClassMeta<T> cm) throws BadRequest, InternalServerError {
618      try {
619         if (cm.isMapOrBean() && isOneOf(name, "*", "")) {
620            ObjectMap m = new ObjectMap();
621            for (Map.Entry<String,String[]> e : this.entrySet()) {
622               String k = e.getKey();
623               HttpPartSchema pschema = schema == null ? null : schema.getProperty(k);
624               ClassMeta<?> cm2 = cm.getValueType();
625               if (cm.getValueType().isCollectionOrArray())
626                  m.put(k, getAllInner(parser, pschema, k, null, cm2));
627               else
628                  m.put(k, getInner(parser, pschema, k, null, cm2));
629            }
630            return req.getBeanSession().convertToType(m, cm);
631         }
632         T t = parse(parser, schema, getString(name), cm);
633         return (t == null ? def : t);
634      } catch (SchemaValidationException e) {
635         throw new BadRequest(e, "Validation failed on query parameter ''{0}''. ", name);
636      } catch (ParseException e) {
637         throw new BadRequest(e, "Could not parse query parameter ''{0}''.", name) ;
638      } catch (Exception e) {
639         throw new InternalServerError(e, "Could not parse query parameter ''{0}''.", name) ;
640      }
641   }
642
643   /* Workhorse method */
644   @SuppressWarnings("rawtypes")
645   private <T> T getAllInner(HttpPartParser parser, HttpPartSchema schema, String name, T def, ClassMeta<T> cm) throws BadRequest, InternalServerError {
646      String[] p = get(name);
647      if (p == null)
648         return def;
649      if (schema == null)
650         schema = HttpPartSchema.DEFAULT;
651      try {
652         if (cm.isArray()) {
653            List c = new ArrayList();
654            for (int i = 0; i < p.length; i++)
655               c.add(parse(parser, schema.getItems(), p[i], cm.getElementType()));
656            return (T)toArray(c, cm.getElementType().getInnerClass());
657         } else if (cm.isCollection()) {
658            Collection c = (Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new ObjectList());
659            for (int i = 0; i < p.length; i++)
660               c.add(parse(parser, schema.getItems(), p[i], cm.getElementType()));
661            return (T)c;
662         }
663      } catch (SchemaValidationException e) {
664         throw new BadRequest(e, "Validation failed on query parameter ''{0}''. ", name);
665      } catch (ParseException e) {
666         throw new BadRequest(e, "Could not parse query parameter ''{0}''.", name) ;
667      } catch (Exception e) {
668         throw new InternalServerError(e, "Could not parse query parameter ''{0}''.", name) ;
669      }
670      throw new InternalServerError("Invalid call to getParameters(String, ClassMeta).  Class type must be a Collection or array.");
671   }
672
673   private <T> T parse(HttpPartParser parser, HttpPartSchema schema, String val, ClassMeta<T> c) throws SchemaValidationException, ParseException {
674      if (parser == null)
675         parser = this.parser;
676      return parser.createPartSession(req.getParserSessionArgs()).parse(HttpPartType.QUERY, schema, val, c);
677   }
678
679   /**
680    * Converts the query parameters to a readable string.
681    *
682    * @param sorted Sort the query parameters by name.
683    * @return A JSON string containing the contents of the query parameters.
684    */
685   public String toString(boolean sorted) {
686      Map<String,Object> m = null;
687      if (sorted)
688         m = new TreeMap<>();
689      else
690         m = new LinkedHashMap<>();
691      for (Map.Entry<String,String[]> e : this.entrySet()) {
692         String[] v = e.getValue();
693         m.put(e.getKey(), v.length == 1 ? v[0] : v);
694      }
695      return SimpleJsonSerializer.DEFAULT.toString(m);
696   }
697
698   /**
699    * Converts this object to a query string.
700    *
701    * <p>
702    * Returned query string does not start with <js>'?'</js>.
703    *
704    * @return A new query string, or an empty string if this object is empty.
705    */
706   public String toQueryString() {
707      StringBuilder sb = new StringBuilder();
708      for (Map.Entry<String,String[]> e : this.entrySet()) {
709         for (int i = 0; i < e.getValue().length; i++) {
710            if (sb.length() > 0)
711               sb.append("&");
712            sb.append(urlEncode(e.getKey())).append('=').append(urlEncode(e.getValue()[i]));
713         }
714      }
715      return sb.toString();
716   }
717
718   @Override /* Object */
719   public String toString() {
720      return toString(false);
721   }
722
723   //-----------------------------------------------------------------------------------------------------------------
724   // Helper methods
725   //-----------------------------------------------------------------------------------------------------------------
726
727   private <T> ClassMeta<T> getClassMeta(Type type, Type...args) {
728      return req.getBeanSession().getClassMeta(type, args);
729   }
730
731   private <T> ClassMeta<T> getClassMeta(Class<T> type) {
732      return req.getBeanSession().getClassMeta(type);
733   }
734}