001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.httppart;
018
019import static org.apache.juneau.commons.httppart.HttpPartType.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022import static org.apache.juneau.commons.utils.StringUtils.*;
023
024import java.util.*;
025import java.util.stream.*;
026
027import org.apache.juneau.commons.collections.*;
028import org.apache.juneau.commons.lang.*;
029import org.apache.juneau.commons.svl.*;
030import org.apache.juneau.commons.utils.*;
031import org.apache.juneau.http.*;
032import org.apache.juneau.http.HttpPart;
033import org.apache.juneau.http.part.*;
034import org.apache.juneau.marshall.httppart.*;
035import org.apache.juneau.rest.server.*;
036
037/**
038 * Represents the query parameters in an HTTP request.
039 *
040 * <p>
041 *    The {@link RequestQueryParamList} object is the API for accessing the GET query parameters of an HTTP request.
042 *    It can be accessed by passing it as a parameter on your REST Java method:
043 * </p>
044 * <p class='bjava'>
045 *    <ja>@RestPost</ja>(...)
046 *    <jk>public</jk> Object myMethod(RequestQueryParamList <jv>query</jv>) {...}
047 * </p>
048 *
049 * <h5 class='figure'>Example:</h5>
050 * <p class='bjava'>
051 *    <ja>@RestPost</ja>(...)
052 *    <jk>public</jk> Object myMethod(RequestQueryParamList <jv>query</jv>) {
053 *
054 *       <jc>// Get query parameters converted to various types.</jc>
055 *       <jk>int</jk> <jv>p1</jv> = <jv>query</jv>.get(<js>"p1"</js>).asInteger().orElse(0);
056 *       String <jv>p2</jv> = <jv>query</jv>.get(<js>"p2"</js>).orElse(<jk>null</jk>);
057 *       UUID <jv>p3</jv> = <jv>query</jv>.get(<js>"p3"</js>).as(UUID.<jk>class</jk>).orElse(<jk>null</jk>);
058 *     }
059 * </p>
060 *
061 * <p>
062 *    An important distinction between the behavior of this object and <l>HttpServletRequest.getParameter(String)</l> is
063 *    that the former will NOT load the content of the request on FORM POSTS and will only look at parameters
064 *    found in the query string.
065 *    This can be useful in cases where you're mixing GET parameters and FORM POSTS and you don't want to
066 *    inadvertently read the content of the request to get a query parameter.
067 * </p>
068 *
069 * <p>
070 *    Some important methods on this class are:
071 * </p>
072 * <ul class='javatree'>
073 *    <li class='jc'>{@link RequestQueryParamList}
074 *    <ul class='spaced-list'>
075 *       <li>Methods for retrieving query parameters:
076 *       <ul class='javatreec'>
077 *          <li class='jm'>{@link RequestQueryParamList#contains(String) contains(String)}
078 *          <li class='jm'>{@link RequestQueryParamList#containsAny(String...) containsAny(String...)}
079 *          <li class='jm'>{@link RequestQueryParamList#get(Class) get(Class)}
080 *          <li class='jm'>{@link RequestQueryParamList#get(String) get(String)}
081 *          <li class='jm'>{@link RequestQueryParamList#getAll(String) getAll(String)}
082 *          <li class='jm'>{@link RequestQueryParamList#getFirst(String) getFirst(String)}
083 *          <li class='jm'>{@link RequestQueryParamList#getLast(String) getLast(String)}
084 *       </ul>
085 *       <li>Methods overridding query parameters:
086 *       <ul class='javatreec'>
087 *          <li class='jm'>{@link RequestQueryParamList#add(HttpPart...) add(HttpPart...)}
088 *          <li class='jm'>{@link RequestQueryParamList#add(String,Object) add(String,Object)}
089 *          <li class='jm'>{@link RequestQueryParamList#addDefault(List) addDefault(List)}
090 *          <li class='jm'>{@link RequestQueryParamList#addDefault(HttpPart...) addDefault(HttpPart...)}
091 *          <li class='jm'>{@link RequestQueryParamList#addDefault(String,String) addDefault(String,String)}
092 *          <li class='jm'>{@link RequestQueryParamList#remove(String) remove(String)}
093 *          <li class='jm'>{@link RequestQueryParamList#set(HttpPart...) set(HttpPart...)}
094 *          <li class='jm'>{@link RequestQueryParamList#set(String,Object) set(String,Object)}
095 *       </ul>
096 *       <li>Other methods:
097 *       <ul class='javatreec'>
098 *          <li class='jm'>{@link RequestQueryParamList#asQueryString() asQueryString()}
099 *          <li class='jm'>{@link RequestQueryParamList#copy() copy()}
100 *          <li class='jm'>{@link RequestQueryParamList#isEmpty() isEmpty()}
101 *       </ul>
102 *    </ul>
103 * </ul>
104 *
105 * <p>
106 * Entries are stored in a case-sensitive map unless overridden via the constructor.
107 *
108 * <h5 class='section'>See Also:</h5><ul>
109 *    <li class='jc'>{@link RequestQueryParam}
110 *    <li class='ja'>{@link Query}
111 *    <li class='ja'>{@link HasQuery}
112 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a>
113 * </ul>
114 */
115@SuppressWarnings({
116   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
117})
118public class RequestQueryParamList extends ArrayList<RequestQueryParam> {
119
120   private static final long serialVersionUID = 1L;
121
122   private final transient RestRequest req;
123   private boolean caseSensitive;
124   private final transient VarResolverSession vs;
125   private transient HttpPartParserSession parser;
126
127   /**
128    * Constructor.
129    *
130    * @param req The request creating this bean.  Must not be <jk>null</jk>.
131    * @param query The raw parsed query parameter values.  Must not be <jk>null</jk>.
132    * @param caseSensitive Whether case-sensitive name matching is enabled.
133    */
134   public RequestQueryParamList(RestRequest req, Map<String,String[]> query, boolean caseSensitive) {
135      this.req = req;
136      this.caseSensitive = caseSensitive;
137      this.vs = req.getVarResolverSession();
138
139      for (var e : query.entrySet()) {
140         var name = e.getKey();
141
142         var values = e.getValue();
143         if (values == null)
144            values = new String[0];
145
146         // Fix for behavior difference between Tomcat and WAS.
147         // getParameter("foo") on "&foo" in Tomcat returns "".
148         // getParameter("foo") on "&foo" in WAS returns null.
149         if (values.length == 1 && values[0] == null)
150            values[0] = "";
151
152         if (values.length == 0)
153            values = a((String)null);
154
155         for (var value : values)
156            add(new RequestQueryParam(req, name, value));
157      }
158   }
159
160   /**
161    * Copy constructor.
162    */
163   private RequestQueryParamList(RequestQueryParamList copyFrom) {
164      req = copyFrom.req;
165      caseSensitive = copyFrom.caseSensitive;
166      parser = copyFrom.parser;
167      addAll(copyFrom);
168      vs = copyFrom.vs;
169   }
170
171   /**
172    * Subset constructor.
173    */
174   private RequestQueryParamList(RequestQueryParamList copyFrom, String...names) {
175      this.req = copyFrom.req;
176      caseSensitive = copyFrom.caseSensitive;
177      parser = copyFrom.parser;
178      vs = copyFrom.vs;
179      for (var n : names)
180         copyFrom.stream().filter(x -> eq(x.getName(), n)).forEach(this::add);
181   }
182
183   /**
184    * Adds request parameter values.
185    *
186    * <p>
187    * Parameters are added to the end.
188    * <br>Existing parameters with the same name are not changed.
189    *
190    * @param parameters The parameter objects.  Must not be <jk>null</jk>.
191    * @return This object.
192    */
193   public RequestQueryParamList add(HttpPart...parameters) {
194      reqnn("parameters", parameters);
195      for (var p : parameters)
196         if (nn(p))
197            add(p.getName(), p.getValue());
198      return this;
199   }
200
201   /**
202    * Adds a parameter value.
203    *
204    * <p>
205    * Parameter is added to the end.
206    * <br>Existing parameter with the same name are not changed.
207    *
208    * @param name The parameter name.  Must not be <jk>null</jk>.
209    * @param value The parameter value.
210    * @return This object.
211    */
212   public RequestQueryParamList add(String name, Object value) {
213      reqnn("name", name);
214      add(new RequestQueryParam(req, name, s(value)).parser(parser));
215      return this;
216   }
217
218   /**
219    * Adds default entries to these parameters.
220    *
221    * <p>
222    * Similar to {@link #set(String, Object)} but doesn't override existing values.
223    *
224    * @param pairs
225    *    The default entries.
226    *    <br>Can be <jk>null</jk> (no-op).
227    * @return This object.
228    */
229   public RequestQueryParamList addDefault(List<? extends HttpPart> pairs) {
230      if (pairs == null)
231         return this;
232      for (var p : pairs) {
233         var name = p.getName();
234         var l = stream(name);
235         var hasAllBlanks = l.allMatch(x -> Shorts.ie(x.getValue()));
236         if (hasAllBlanks) {
237            removeAll(getAll(name));
238            add(new RequestQueryParam(req, name, vs.resolve(p.getValue())));
239         }
240      }
241      return this;
242   }
243
244   /**
245    * Adds default entries to these parameters.
246    *
247    * <p>
248    * Similar to {@link #set(String, Object)} but doesn't override existing values.
249    *
250    * @param pairs
251    *    The default entries.
252    *    <br>Can be <jk>null</jk> (no-op).
253    * @return This object.
254    */
255   public RequestQueryParamList addDefault(HttpPart...pairs) {
256      return addDefault(l(pairs));
257   }
258
259   /**
260    * Adds a default entry to the query parameters.
261    *
262    * @param name The name.
263    * @param value The value.
264    * @return This object.
265    */
266   public RequestQueryParamList addDefault(String name, String value) {
267      return addDefault(HttpStringPart.of(name, value));
268   }
269
270   /**
271    * Converts this object to a query string.
272    *
273    * <p>
274    * Returned query string does not start with <js>'?'</js>.
275    *
276    * @return A new query string, or an empty string if this object is empty.
277    */
278   public String asQueryString() {
279      var sb = new StringBuilder();
280      for (var e : this) {
281         if (!sb.isEmpty())
282            sb.append("&");
283         sb.append(urlEncode(e.getName())).append('=').append(urlEncode(e.getValue()));
284      }
285      return sb.toString();
286   }
287
288   /**
289    * Sets case sensitivity for names in this list.
290    *
291    * @param value The new value for this setting.
292    * @return This object (for method chaining).
293    */
294   public RequestQueryParamList caseSensitive(boolean value) {
295      caseSensitive = value;
296      return this;
297   }
298
299   /**
300    * Returns <jk>true</jk> if the parameters with the specified name is present.
301    *
302    * @param name The parameter name.  Must not be <jk>null</jk>.
303    * @return <jk>true</jk> if the parameters with the specified names are present.
304    */
305   public boolean contains(String name) {
306      return stream(name).findAny().isPresent();
307   }
308
309   /**
310    * Returns <jk>true</jk> if the parameter with any of the specified names are present.
311    *
312    * @param names The parameter names.  Must not be <jk>null</jk>.
313    * @return <jk>true</jk> if the parameter with any of the specified names are present.
314    */
315   public boolean containsAny(String...names) {
316      reqnn("names", names);
317      for (var n : names)
318         if (stream(n).findAny().isPresent())
319            return true;
320      return false;
321   }
322
323   /**
324    * Makes a copy of these parameters.
325    *
326    * @return A new parameters object.
327    */
328   public RequestQueryParamList copy() {
329      return new RequestQueryParamList(this);
330   }
331
332   /**
333    * Returns the query parameter as the specified bean type.
334    *
335    * <p>
336    * Type must have a name specified via the {@link Query} annotation
337    * and a public constructor that takes in either <c>value</c> or <c>name,value</c> as strings.
338    *
339    * @param <T> The bean type to create.
340    * @param type The bean type to create.
341    * @return The bean, or {@link Optional#empty()} if the parameter is not present.
342    */
343   public <T> Optional<T> get(Class<T> type) {
344      var cm = req.getMarshallingSession().getClassMeta(type);
345      var name = HttpParts.getName(QUERY, cm).orElseThrow(() -> rex("@Query(name) not found on class %s", cn(type)));
346      return get(name).as(type);
347   }
348
349   /**
350    * Returns the condensed header with the specified name.
351    *
352    * <p>
353    * If multiple headers are present, they will be combined into a single comma-delimited list.
354    *
355    * @param name The header name.
356    * @return The header, never <jk>null</jk>.
357    */
358   public RequestQueryParam get(String name) {
359      List<RequestQueryParam> l = getAll(name);
360      if (l.isEmpty())
361         return new RequestQueryParam(req, name, null).parser(parser);
362      if (l.size() == 1)
363         return l.get(0);
364      var sb = new StringBuilder(128);
365      for (var i = 0; i < l.size(); i++) {
366         if (i > 0)
367            sb.append(", ");
368         sb.append(l.get(i).getValue());
369      }
370      return new RequestQueryParam(req, name, sb.toString()).parser(parser);
371   }
372
373   /**
374    * Returns all the parameters with the specified name.
375    *
376    * @param name The parameter name.
377    * @return The list of all parameters with the specified name, or an empty list if none are found.
378    *    <br>List is unmodifiable.
379    */
380   public List<RequestQueryParam> getAll(String name) {
381      return stream(name).toList();
382   }
383
384   /**
385    * Returns the first parameter with the specified name.
386    *
387    * <p>
388    * Note that this method never returns <jk>null</jk> and that {@link RequestQueryParam#isPresent()} can be used
389    * to test for the existence of the parameter.
390    *
391    * @param name The parameter name.
392    * @return The parameter.  Never <jk>null</jk>.
393    */
394   public RequestQueryParam getFirst(String name) {
395      reqnn("name", name);
396      return stream(name).findFirst().orElseGet(() -> new RequestQueryParam(req, name, null).parser(parser));
397   }
398
399   /**
400    * Returns the last parameter with the specified name.
401    *
402    * <p>
403    * Note that this method never returns <jk>null</jk> and that {@link RequestQueryParam#isPresent()} can be used
404    * to test for the existence of the parameter.
405    *
406    * @param name The parameter name.
407    * @return The parameter.  Never <jk>null</jk>.
408    */
409   public RequestQueryParam getLast(String name) {
410      reqnn("name", name);
411      var v = Holder.<RequestQueryParam>empty();
412      stream(name).forEach(v::set);
413      return v.orElseGet(() -> new RequestQueryParam(req, name, null).parser(parser));
414   }
415
416   /**
417    * Returns all the unique header names in this list.
418    * @return The list of all unique header names in this list.
419    *    <br>List is unmodifiable.
420    */
421   public List<String> getNames() { return stream().map(RequestQueryParam::getName).map(x -> caseSensitive ? x : lcr(x)).distinct().toList(); }
422
423   /**
424    * Returns all headers in sorted order.
425    *
426    * @return The stream of all headers in sorted order.
427    */
428   public Stream<RequestQueryParam> getSorted() {
429      Comparator<RequestQueryParam> x;
430      if (caseSensitive)
431         x = Comparator.comparing(RequestQueryParam::getName);
432      else
433         x = (x1, x2) -> String.CASE_INSENSITIVE_ORDER.compare(x1.getName(), x2.getName());
434      return stream().sorted(x);
435   }
436
437   /**
438    * Sets the parser to use for part values.
439    *
440    * @param value The new value for this setting.
441    * @return This object.
442    */
443   public RequestQueryParamList parser(HttpPartParserSession value) {
444      parser = value;
445      forEach(x -> x.parser(parser));
446      return this;
447   }
448
449   /**
450    * Remove parameters.
451    *
452    * @param name The parameter names.  Must not be <jk>null</jk>.
453    * @return This object.
454    */
455   public RequestQueryParamList remove(String name) {
456      reqnn("name", name);
457      removeIf(x -> eq(x.getName(), name));
458      return this;
459   }
460
461   /**
462    * Sets request header values.
463    *
464    * <p>
465    * Parameters are added to the end of the headers.
466    * <br>Any previous parameters with the same name are removed.
467    *
468    * @param parameters The parameters to set.  Must not be <jk>null</jk> or contain <jk>null</jk>.
469    * @return This object.
470    */
471   public RequestQueryParamList set(HttpPart...parameters) {
472      reqnn("headers", parameters);
473      for (var p : parameters)
474         remove(p.getName());
475      for (var p : parameters)
476         add(p);
477      return this;
478   }
479
480   /**
481    * Sets a parameter value.
482    *
483    * <p>
484    * Parameter is added to the end.
485    * <br>Any previous parameters with the same name are removed.
486    *
487    * @param name The parameter name.  Must not be <jk>null</jk>.
488    * @param value
489    *    The parameter value.
490    *    <br>Converted to a string using {@link Object#toString()}.
491    *    <br>Can be <jk>null</jk> (the parameter is set with a <jk>null</jk> value).
492    * @return This object.
493    */
494   public RequestQueryParamList set(String name, Object value) {
495      reqnn("name", name);
496      set(new RequestQueryParam(req, name, s(value)).parser(parser));
497      return this;
498   }
499
500   /**
501    * Returns all headers with the specified name.
502    *
503    * @param name The header name.
504    * @return The stream of all headers with matching names.  Never <jk>null</jk>.
505    */
506   public Stream<RequestQueryParam> stream(String name) {
507      return stream().filter(x -> eq(x.getName(), name));
508   }
509
510   /**
511    * Returns a copy of this object but only with the specified param names copied.
512    *
513    * @param names The list to include in the copy.
514    * @return A new list object.
515    */
516   public RequestQueryParamList subset(String...names) {
517      return new RequestQueryParamList(this, names);
518   }
519
520   protected FluentMap<String,Object> properties() {
521      // @formatter:off
522      var m = filteredBeanPropertyMap();
523      for (var n : getNames())
524         m.a(n, get(n).asString().orElse(null));
525      return m;
526      // @formatter:on
527   }
528
529   @Override
530   public boolean equals(Object o) {
531      return this == o || (o instanceof RequestQueryParamList o2 && super.equals(o2));
532   }
533
534   @Override
535   public int hashCode() {
536      return super.hashCode();
537   }
538
539   @Override /* Overridden from Object */
540   public String toString() {
541      return r(properties());
542   }
543
544   private boolean eq(String s1, String s2) {
545      return Shorts.eq(! caseSensitive, s1, s2);  // NOAI
546   }
547}