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.httppart;
014
015import static org.apache.juneau.internal.ClassUtils.*;
016import static org.apache.juneau.internal.CollectionUtils.*;
017import static java.util.stream.Collectors.*;
018import static org.apache.juneau.common.internal.ArgUtils.*;
019import static org.apache.juneau.common.internal.StringUtils.*;
020import static org.apache.juneau.httppart.HttpPartType.*;
021
022import java.util.*;
023import java.util.stream.*;
024
025import org.apache.http.*;
026import org.apache.juneau.httppart.*;
027import org.apache.juneau.rest.*;
028import org.apache.juneau.svl.*;
029import org.apache.juneau.*;
030import org.apache.juneau.collections.*;
031import org.apache.juneau.common.internal.*;
032import org.apache.juneau.http.*;
033import org.apache.juneau.http.header.*;
034
035/**
036 * Represents the headers in an HTTP request.
037 *
038 * <p>
039 *    The {@link RequestHeaders} object is the API for accessing the headers of an HTTP request.
040 *    It can be accessed by passing it as a parameter on your REST Java method:
041 * </p>
042 * <p class='bjava'>
043 *    <ja>@RestPost</ja>(...)
044 *    <jk>public</jk> Object myMethod(RequestHeaders <jv>headers</jv>) {...}
045 * </p>
046 *
047 * <h5 class='figure'>Example:</h5>
048 * <p class='bjava'>
049 *    <ja>@RestPost</ja>(...)
050 *    <jk>public</jk> Object myMethod(RequestHeaders <jv>headers</jv>) {
051 *
052 *       <jc>// Add a default value.</jc>
053 *       <jv>headers</jv>.addDefault(<js>"ETag"</js>, <jsf>DEFAULT_UUID</jsf>);
054 *
055 *       <jc>// Get a header value as a POJO.</jc>
056 *       UUID <jv>etag</jv> = <jv>headers</jv>.get(<js>"ETag"</js>).as(UUID.<jk>class</jk>).get();
057 *
058 *       <jc>// Get a header as a standard HTTP part.</jc>
059 *       ContentType <jv>contentType</jv> = <jv>headers</jv>.get(ContentType.<jk>class</jk>).orElse(ContentType.<jsf>TEXT_XML</jsf>);
060 *    }
061 * </p>
062 *
063 * <p>
064 *    Some important methods on this class are:
065 * </p>
066 * <ul class='javatree'>
067 *    <li class='jc'>{@link RequestHeaders}
068 *    <ul class='spaced-list'>
069 *       <li>Methods for retrieving headers:
070 *       <ul class='javatreec'>
071 *          <li class='jm'>{@link RequestHeaders#contains(String) contains(String)}
072 *          <li class='jm'>{@link RequestHeaders#containsAny(String...) containsAny(String...)}
073 *          <li class='jm'>{@link RequestHeaders#get(Class) get(Class)}
074 *          <li class='jm'>{@link RequestHeaders#get(String) get(String)}
075 *          <li class='jm'>{@link RequestHeaders#getAll(String) getAll(String)}
076 *          <li class='jm'>{@link RequestHeaders#getFirst(String) getFirst(String)}
077 *          <li class='jm'>{@link RequestHeaders#getLast(String) getLast(String)}
078 *       </ul>
079 *       <li>Methods overridding headers:
080 *       <ul class='javatreec'>
081 *          <li class='jm'>{@link RequestHeaders#add(Header...) add(Header...)}
082 *          <li class='jm'>{@link RequestHeaders#add(String, Object) add(String, Object)}
083 *          <li class='jm'>{@link RequestHeaders#addDefault(Header...) addDefault(Header...)}
084 *          <li class='jm'>{@link RequestHeaders#addDefault(List) addDefault(List)}
085 *          <li class='jm'>{@link RequestHeaders#addDefault(String,String) addDefault(String,String)}
086 *          <li class='jm'>{@link RequestHeaders#remove(String) remove(String)}
087 *          <li class='jm'>{@link RequestHeaders#set(Header...) set(Header...)}
088 *          <li class='jm'>{@link RequestHeaders#set(String,Object) set(String,Object)}
089 *       </ul>
090 *       <li>Other methods:
091 *       <ul class='javatreec'>
092 *          <li class='jm'>{@link RequestHeaders#copy() copy()}
093 *          <li class='jm'>{@link RequestHeaders#isEmpty() isEmpty()}
094 *          <li class='jm'>{@link RequestHeaders#subset(String...) subset(String...)}
095 *       </ul>
096 *    </ul>
097 * </ul>
098 *
099 * <p>
100 * Entries are stored in a case-insensitive map unless overridden via the constructor.
101 *
102 * <h5 class='section'>See Also:</h5><ul>
103 *    <li class='jc'>{@link RequestHeader}
104 *    <li class='ja'>{@link org.apache.juneau.http.annotation.Header}
105 *    <li class='link'><a class="doclink" href="../../../../../index.html#jrs.HttpParts">HTTP Parts</a>
106 * </ul>
107 */
108public class RequestHeaders extends ArrayList<RequestHeader> {
109
110   private static final long serialVersionUID = 1L;
111
112   private final RestRequest req;
113   private boolean caseSensitive;
114   private final VarResolverSession vs;
115
116   private HttpPartParserSession parser;
117
118   /**
119    * Constructor.
120    *
121    * @param req The request creating this bean.
122    * @param query The query parameters on the request (used for overloaded header values).
123    * @param caseSensitive Whether case-sensitive name matching is enabled.
124    */
125   public RequestHeaders(RestRequest req, RequestQueryParams query, boolean caseSensitive) {
126      this.req = req;
127      this.caseSensitive = caseSensitive;
128      this.vs = req.getVarResolverSession();
129
130      for (Enumeration<String> e = req.getHttpServletRequest().getHeaderNames(); e.hasMoreElements();) {
131         String name = e.nextElement();
132         for (Enumeration<String> ve = req.getHttpServletRequest().getHeaders(name); ve.hasMoreElements();) {
133            add(new RequestHeader(req, name, ve.nextElement()));
134         }
135      }
136
137      // Parameters defined on the request URL overwrite existing headers.
138      Set<String> allowedHeaderParams = req.getContext().getAllowedHeaderParams();
139      query.forEach(p -> {
140         String name = p.getName();
141         String key = key(name);
142         if (allowedHeaderParams.contains(key) || allowedHeaderParams.contains("*")) {
143            set(name, p.getValue());
144         }
145      });
146   }
147
148   /**
149    * Copy constructor.
150    */
151   private RequestHeaders(RequestHeaders copyFrom) {
152      req = copyFrom.req;
153      caseSensitive = copyFrom.caseSensitive;
154      parser = copyFrom.parser;
155      addAll(copyFrom);
156      vs = copyFrom.vs;
157   }
158
159   /**
160    * Subset constructor.
161    */
162   private RequestHeaders(RequestHeaders copyFrom, String...names) {
163      this.req = copyFrom.req;
164      caseSensitive = copyFrom.caseSensitive;
165      parser = copyFrom.parser;
166      vs = copyFrom.vs;
167      for (String n : names)
168         copyFrom.stream().filter(x -> eq(x.getName(), n)).forEach(x -> add(x));
169   }
170
171   /**
172    * Sets the parser to use for part values.
173    *
174    * @param value The new value for this setting.
175    * @return This object.
176    */
177   public RequestHeaders parser(HttpPartParserSession value) {
178      this.parser = value;
179      forEach(x -> x.parser(parser));
180      return this;
181   }
182
183   /**
184    * Sets case sensitivity for names in this list.
185    *
186    * @param value The new value for this setting.
187    * @return This object (for method chaining).
188    */
189   public RequestHeaders caseSensitive(boolean value) {
190      this.caseSensitive = value;
191      return this;
192   }
193
194   //-----------------------------------------------------------------------------------------------------------------
195   // Basic operations.
196   //-----------------------------------------------------------------------------------------------------------------
197
198   /**
199    * Adds default entries to these headers.
200    *
201    * <p>
202    * Similar to {@link #set(String, Object)} but doesn't override existing values.
203    *
204    * @param pairs The default entries.  Must not be <jk>null</jk>.
205    * @return This object.
206    */
207   public RequestHeaders addDefault(List<Header> pairs) {
208      assertArgNotNull("pairs", pairs);
209      for (Header p : pairs) {
210         String name = p.getName();
211         Stream<RequestHeader> l = stream(name);
212         boolean hasAllBlanks = l.allMatch(x -> StringUtils.isEmpty(x.getValue()));
213         if (hasAllBlanks) {
214            removeAll(getAll(name));
215            add(new RequestHeader(req, name, vs.resolve(p.getValue())));
216         }
217      }
218      return this;
219   }
220
221   /**
222    * Adds default entries to these headers.
223    *
224    * <p>
225    * Similar to {@link #set(String, Object)} but doesn't override existing values.
226    *
227    * @param pairs The default entries.  Must not be <jk>null</jk>.
228    * @return This object.
229    */
230   public RequestHeaders addDefault(Header...pairs) {
231      return addDefault(alist(pairs));
232   }
233
234   /**
235    * Adds a default entry to the request headers.
236    *
237    * @param name The name.
238    * @param value The value.
239    * @return This object.
240    */
241   public RequestHeaders addDefault(String name, String value) {
242      return addDefault(BasicStringHeader.of(name, value));
243   }
244
245   /**
246    * Adds a request header value.
247    *
248    * <p>
249    * Header is added to the end.
250    * <br>Existing headers with the same name are not changed.
251    *
252    * @param name The header name.  Must not be <jk>null</jk>.
253    * @param value The header value.  Can be <jk>null</jk>.
254    * @return This object.
255    */
256   public RequestHeaders add(String name, Object value) {
257      assertArgNotNull("name", name);
258      add(new RequestHeader(req, name, stringify(value)).parser(parser));
259      return this;
260   }
261
262   /**
263    * Adds request header values.
264    *
265    * <p>
266    * Headers are added to the end.
267    * <br>Existing headers with the same name are not changed.
268    *
269    * @param headers The header objects.  Must not be <jk>null</jk>.
270    * @return This object.
271    */
272   public RequestHeaders add(Header...headers) {
273      assertArgNotNull("headers", headers);
274      for (Header h : headers)
275         if (h != null)
276            add(h.getName(), h.getValue());
277      return this;
278   }
279
280   /**
281    * Sets a request header value.
282    *
283    * <p>
284    * Header is added to the end.
285    * <br>Any previous headers with the same name are removed.
286    *
287    * @param name The header name.  Must not be <jk>null</jk>.
288    * @param value
289    *    The header value.
290    *    <br>Converted to a string using {@link Object#toString()}.
291    *    <br>Can be <jk>null</jk>.
292    * @return This object.
293    */
294   public RequestHeaders set(String name, Object value) {
295      assertArgNotNull("name", name);
296      set(new RequestHeader(req, name, stringify(value)).parser(parser));
297      return this;
298   }
299
300   /**
301    * Sets request header values.
302    *
303    * <p>
304    * Headers are added to the end.
305    * <br>Any previous headers with the same name are removed.
306    *
307    * @param headers The header to set.  Must not be <jk>null</jk> or contain <jk>null</jk>.
308    * @return This object.
309    */
310   public RequestHeaders set(Header...headers) {
311      assertArgNotNull("headers", headers);
312      for (Header h : headers)
313         remove(h);
314      for (Header h : headers)
315         add(h);
316      return this;
317   }
318
319   /**
320    * Remove header by name.
321    *
322    * @param name The header names.  Must not be <jk>null</jk>.
323    * @return This object.
324    */
325   public RequestHeaders remove(String name) {
326      assertArgNotNull("name", name);
327      removeIf(x -> eq(x.getName(), name));
328      return this;
329   }
330
331   /**
332    * Returns a copy of this object but only with the specified header names copied.
333    *
334    * @param names The list to include in the copy.
335    * @return A new list object.
336    */
337   public RequestHeaders subset(String...names) {
338      return new RequestHeaders(this, names);
339   }
340
341   //-----------------------------------------------------------------------------------------------------------------
342   // Convenience getters.
343   //-----------------------------------------------------------------------------------------------------------------
344
345   /**
346    * Returns <jk>true</jk> if the header with the specified name is present.
347    *
348    * @param name The header name.  Must not be <jk>null</jk>.
349    * @return <jk>true</jk> if the header with the specified name is present.
350    */
351   public boolean contains(String name) {
352      return stream(name).findAny().isPresent();
353   }
354
355   /**
356    * Returns <jk>true</jk> if the header with any of the specified names are present.
357    *
358    * @param names The header names.  Must not be <jk>null</jk>.
359    * @return <jk>true</jk> if the header with any of the specified names are present.
360    */
361   public boolean containsAny(String...names) {
362      assertArgNotNull("names", names);
363      for (String n : names)
364         if (stream(n).findAny().isPresent())
365            return true;
366      return false;
367   }
368
369   /**
370    * Returns all headers with the specified name.
371    *
372    * @param name The header name.
373    * @return The list of all headers with matching names.  Never <jk>null</jk>.
374    */
375   public List<RequestHeader> getAll(String name) {
376      return stream(name).collect(toList());
377   }
378
379   /**
380    * Returns all headers with the specified name.
381    *
382    * @param name The header name.
383    * @return The stream of all headers with matching names.  Never <jk>null</jk>.
384    */
385   public Stream<RequestHeader> stream(String name) {
386      return stream().filter(x -> eq(x.getName(), name));
387   }
388
389   /**
390    * Returns all headers in sorted order.
391    *
392    * @return The stream of all headers in sorted order.
393    */
394   public Stream<RequestHeader> getSorted() {
395      Comparator<RequestHeader> x;
396      if (caseSensitive)
397         x = (x1,x2) -> x1.getName().compareTo(x2.getName());
398      else
399         x = (x1,x2) -> String.CASE_INSENSITIVE_ORDER.compare(x1.getName(), x2.getName());
400      return stream().sorted(x);
401   }
402
403   /**
404    * Returns all the unique header names in this list.
405    * @return The list of all unique header names in this list.
406    */
407   public List<String> getNames() {
408      return stream().map(x -> x.getName()).map(x -> caseSensitive ? x : x.toLowerCase()).distinct().collect(toList());
409   }
410
411   /**
412    * Returns the first header with the specified name.
413    *
414    * <p>
415    * Note that this method never returns <jk>null</jk> and that {@link RequestHeader#isPresent()} can be used
416    * to test for the existence of the header.
417    *
418    * @param name The header name.  Must not be <jk>null</jk>.
419    * @return The header.  Never <jk>null</jk>.
420    */
421   public RequestHeader getFirst(String name) {
422      assertArgNotNull("name", name);
423      return stream(name).findFirst().orElseGet(()->new RequestHeader(req, name, null).parser(parser));
424   }
425
426   /**
427    * Returns the last header with the specified name.
428    *
429    * <p>
430    * Note that this method never returns <jk>null</jk> and that {@link RequestHeader#isPresent()} can be used
431    * to test for the existence of the header.
432    *
433    * @param name The header name.  Must not be <jk>null</jk>.
434    * @return The header.  Never <jk>null</jk>.
435    */
436   public RequestHeader getLast(String name) {
437      assertArgNotNull("name", name);
438      Value<RequestHeader> v = Value.empty();
439      stream(name).forEach(x -> v.set(x));
440      return v.orElseGet(() -> new RequestHeader(req, name, null).parser(parser));
441   }
442
443   /**
444    * Returns the condensed header with the specified name.
445    *
446    * <p>
447    * If multiple headers are present, they will be combined into a single comma-delimited list.
448    *
449    * @param name The header name.
450    * @return The header, never <jk>null</jk>.
451    */
452   public RequestHeader get(String name) {
453      List<RequestHeader> l = getAll(name);
454      if (l.isEmpty())
455         return new RequestHeader(req, name, null).parser(parser);
456      if (l.size() == 1)
457         return l.get(0);
458      StringBuilder sb = new StringBuilder(128);
459      for (int i = 0, j = l.size(); i < j; i++) {
460         if (i > 0)
461            sb.append(", ");
462         sb.append(l.get(i).getValue());
463      }
464      return new RequestHeader(req, name, sb.toString()).parser(parser);
465   }
466
467   /**
468    * Returns the header as the specified bean type.
469    *
470    * <p>
471    * Type must have a name specified via the {@link org.apache.juneau.http.annotation.Header} annotation
472    * and a public constructor that takes in either <c>value</c> or <c>name,value</c> as strings.
473    *
474    * @param <T> The bean type to create.
475    * @param type The bean type to create.
476    * @return The bean, never <jk>null</jk>.
477    */
478   public <T> Optional<T> get(Class<T> type) {
479      ClassMeta<T> cm = req.getBeanSession().getClassMeta(type);
480      String name = HttpParts.getName(HEADER, cm).orElseThrow(()->new BasicRuntimeException("@Header(name) not found on class {0}", className(type)));
481      return get(name).as(type);
482   }
483
484   //-----------------------------------------------------------------------------------------------------------------
485   // Other methods
486   //-----------------------------------------------------------------------------------------------------------------
487
488   /**
489    * Makes a copy of these parameters.
490    *
491    * @return A new parameters object.
492    */
493   public RequestHeaders copy() {
494      return new RequestHeaders(this);
495   }
496
497   private String key(String name) {
498      return caseSensitive ? name : name.toLowerCase();
499   }
500
501   private boolean eq(String s1, String s2) {
502      if (caseSensitive)
503         return StringUtils.eq(s1, s2);
504      return StringUtils.eqic(s1, s2);
505   }
506
507   @Override /* Object */
508   public String toString() {
509      JsonMap m = new JsonMap();
510      for (String n : getNames())
511         m.put(n, get(n).asString().orElse(null));
512      return m.asJson();
513   }
514}