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.*;
022
023import java.util.*;
024import java.util.stream.*;
025
026import org.apache.juneau.commons.collections.*;
027import org.apache.juneau.commons.lang.*;
028import org.apache.juneau.commons.svl.*;
029import org.apache.juneau.commons.utils.*;
030import org.apache.juneau.http.*;
031import org.apache.juneau.http.header.*;
032import org.apache.juneau.marshall.httppart.*;
033import org.apache.juneau.rest.server.*;
034
035/**
036 * Represents the headers in an HTTP request.
037 *
038 * <p>
039 *    The {@link RequestHeaderList} 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(RequestHeaderList <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(RequestHeaderList <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 RequestHeaderList}
068 *    <ul class='spaced-list'>
069 *       <li>Methods for retrieving headers:
070 *       <ul class='javatreec'>
071 *          <li class='jm'>{@link RequestHeaderList#contains(String) contains(String)}
072 *          <li class='jm'>{@link RequestHeaderList#containsAny(String...) containsAny(String...)}
073 *          <li class='jm'>{@link RequestHeaderList#get(Class) get(Class)}
074 *          <li class='jm'>{@link RequestHeaderList#get(String) get(String)}
075 *          <li class='jm'>{@link RequestHeaderList#getAll(String) getAll(String)}
076 *          <li class='jm'>{@link RequestHeaderList#getFirst(String) getFirst(String)}
077 *          <li class='jm'>{@link RequestHeaderList#getLast(String) getLast(String)}
078 *       </ul>
079 *       <li>Methods overridding headers:
080 *       <ul class='javatreec'>
081 *          <li class='jm'>{@link RequestHeaderList#add(HttpHeader...) add(HttpHeader...)}
082 *          <li class='jm'>{@link RequestHeaderList#add(String, Object) add(String, Object)}
083 *          <li class='jm'>{@link RequestHeaderList#addDefault(HttpHeader...) addDefault(HttpHeader...)}
084 *          <li class='jm'>{@link RequestHeaderList#addDefault(List) addDefault(List)}
085 *          <li class='jm'>{@link RequestHeaderList#addDefault(String,String) addDefault(String,String)}
086 *          <li class='jm'>{@link RequestHeaderList#remove(String) remove(String)}
087 *          <li class='jm'>{@link RequestHeaderList#set(HttpHeader...) set(HttpHeader...)}
088 *          <li class='jm'>{@link RequestHeaderList#set(String,Object) set(String,Object)}
089 *       </ul>
090 *       <li>Other methods:
091 *       <ul class='javatreec'>
092 *          <li class='jm'>{@link RequestHeaderList#copy() copy()}
093 *          <li class='jm'>{@link RequestHeaderList#isEmpty() isEmpty()}
094 *          <li class='jm'>{@link RequestHeaderList#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 Header}
105 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a>
106 * </ul>
107 */
108@SuppressWarnings({
109   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
110})
111public class RequestHeaderList extends ArrayList<RequestHeader> {
112
113   private static final long serialVersionUID = 1L;
114
115   private final transient RestRequest req;
116   private boolean caseSensitive;
117   private final transient VarResolverSession vs;
118
119   private transient HttpPartParserSession parser;
120
121   /**
122    * Constructor.
123    *
124    * @param req The request creating this bean.  Must not be <jk>null</jk>.
125    * @param query The query parameters on the request (used for overloaded header values).  Must not be <jk>null</jk>.
126    * @param caseSensitive Whether case-sensitive name matching is enabled.
127    */
128   public RequestHeaderList(RestRequest req, RequestQueryParamList query, boolean caseSensitive) {
129      this.req = req;
130      this.caseSensitive = caseSensitive;
131      this.vs = req.getVarResolverSession();
132
133      for (var e = req.getHttpServletRequest().getHeaderNames(); e.hasMoreElements();) {
134         var name = e.nextElement();
135         for (var ve = req.getHttpServletRequest().getHeaders(name); ve.hasMoreElements();) {
136            add(new RequestHeader(req, name, ve.nextElement()));
137         }
138      }
139
140      // Parameters defined on the request URL are appended to any existing header values.
141      var allowedHeaderParams = req.getContext().getAllowedHeaderParams();
142      query.forEach(p -> {
143         var name = p.getName();
144         var key = key(name);
145         if (allowedHeaderParams.contains(key) || allowedHeaderParams.contains("*")) {
146            add(new RequestHeader(req, name, p.getValue()));
147         }
148      });
149   }
150
151   /**
152    * Copy constructor.
153    */
154   private RequestHeaderList(RequestHeaderList copyFrom) {
155      req = copyFrom.req;
156      caseSensitive = copyFrom.caseSensitive;
157      parser = copyFrom.parser;
158      addAll(copyFrom);
159      vs = copyFrom.vs;
160   }
161
162   /**
163    * Subset constructor.
164    */
165   private RequestHeaderList(RequestHeaderList copyFrom, String...names) {
166      this.req = copyFrom.req;
167      caseSensitive = copyFrom.caseSensitive;
168      parser = copyFrom.parser;
169      vs = copyFrom.vs;
170      for (var n : names)
171         copyFrom.stream().filter(x -> eq(x.getName(), n)).forEach(this::add);
172   }
173
174   /**
175    * Adds request header values.
176    *
177    * <p>
178    * Headers are added to the end.
179    * <br>Existing headers with the same name are not changed.
180    *
181    * @param headers The header objects.  Must not be <jk>null</jk>.
182    * @return This object.
183    */
184   public RequestHeaderList add(HttpHeader...headers) {
185      reqnn("headers", headers);
186      for (var h : headers)
187         if (nn(h))
188            add(h.getName(), h.getValue());
189      return this;
190   }
191
192   /**
193    * Adds a request header value.
194    *
195    * <p>
196    * Header is added to the end.
197    * <br>Existing headers with the same name are not changed.
198    *
199    * @param name The header name.  Must not be <jk>null</jk>.
200    * @param value The header value.  Can be <jk>null</jk> (the header is added with a <jk>null</jk> value).
201    * @return This object.
202    */
203   public RequestHeaderList add(String name, Object value) {
204      reqnn("name", name);
205      add(new RequestHeader(req, name, s(value)).parser(parser));
206      return this;
207   }
208
209   /**
210    * Adds default entries to these headers.
211    *
212    * <p>
213    * Similar to {@link #set(String, Object)} but doesn't override existing values.
214    *
215    * @param pairs The default entries.  Must not be <jk>null</jk>.
216    * @return This object.
217    */
218   public RequestHeaderList addDefault(HttpHeader...pairs) {
219      return addDefault(l(pairs));
220   }
221
222   /**
223    * Adds default entries to these headers.
224    *
225    * <p>
226    * Similar to {@link #set(String, Object)} but doesn't override existing values.
227    *
228    * @param pairs The default entries.  Must not be <jk>null</jk>.
229    * @return This object.
230    */
231   public RequestHeaderList addDefault(List<HttpHeader> pairs) {
232      reqnn("pairs", pairs);
233      for (var p : pairs) {
234         var name = p.getName();
235         var l = stream(name);
236         var hasAllBlanks = l.allMatch(x -> Shorts.ie(x.getValue()));
237         if (hasAllBlanks) {
238            removeAll(getAll(name));
239            add(new RequestHeader(req, name, vs.resolve(p.getValue())));
240         }
241      }
242      return this;
243   }
244
245   /**
246    * Adds a default entry to the request headers.
247    *
248    * @param name The name.
249    * @param value The value.
250    * @return This object.
251    */
252   public RequestHeaderList addDefault(String name, String value) {
253      return addDefault(HttpStringHeader.of(name, value));
254   }
255
256   /**
257    * Sets case sensitivity for names in this list.
258    *
259    * @param value The new value for this setting.
260    * @return This object (for method chaining).
261    */
262   public RequestHeaderList caseSensitive(boolean value) {
263      caseSensitive = value;
264      return this;
265   }
266
267   /**
268    * Returns <jk>true</jk> if the header with the specified name is present.
269    *
270    * @param name The header name.  Must not be <jk>null</jk>.
271    * @return <jk>true</jk> if the header with the specified name is present.
272    */
273   public boolean contains(String name) {
274      return stream(name).findAny().isPresent();
275   }
276
277   /**
278    * Returns <jk>true</jk> if the header with any of the specified names are present.
279    *
280    * @param names The header names.  Must not be <jk>null</jk>.
281    * @return <jk>true</jk> if the header with any of the specified names are present.
282    */
283   public boolean containsAny(String...names) {
284      reqnn("names", names);
285      for (var n : names)
286         if (stream(n).findAny().isPresent())
287            return true;
288      return false;
289   }
290
291   /**
292    * Makes a copy of these parameters.
293    *
294    * @return A new parameters object.
295    */
296   public RequestHeaderList copy() {
297      return new RequestHeaderList(this);
298   }
299
300   /**
301    * Returns the header as the specified bean type.
302    *
303    * <p>
304    * Type must have a name specified via the {@link Header} annotation
305    * and a public constructor that takes in either <c>value</c> or <c>name,value</c> as strings.
306    *
307    * @param <T> The bean type to create.
308    * @param type The bean type to create.
309    * @return The bean, or {@link Optional#empty()} if the header is not present.
310    */
311   public <T> Optional<T> get(Class<T> type) {
312      var cm = req.getMarshallingSession().getClassMeta(type);
313      var name = HttpParts.getName(HEADER, cm).orElseThrow(() -> rex("@Header(name) not found on class %s", cn(type)));
314      return get(name).as(type);
315   }
316
317   /**
318    * Returns the condensed header with the specified name.
319    *
320    * <p>
321    * If multiple headers are present, they will be combined into a single comma-delimited list.
322    *
323    * @param name The header name.
324    * @return The header, never <jk>null</jk>.
325    */
326   public RequestHeader get(String name) {
327      List<RequestHeader> l = getAll(name);
328      if (l.isEmpty())
329         return new RequestHeader(req, name, null).parser(parser);
330      if (l.size() == 1)
331         return l.get(0);
332      var sb = new StringBuilder(128);
333      for (var i = 0; i < l.size(); i++) {
334         if (i > 0)
335            sb.append(", ");
336         sb.append(l.get(i).getValue());
337      }
338      return new RequestHeader(req, name, sb.toString()).parser(parser);
339   }
340
341   /**
342    * Returns all headers with the specified name.
343    *
344    * @param name The header name.
345    * @return The list of all headers with matching names.  Never <jk>null</jk>.
346    *    <br>List is unmodifiable.
347    */
348   public List<RequestHeader> getAll(String name) {
349      return stream(name).toList();
350   }
351
352   /**
353    * Returns the first header with the specified name.
354    *
355    * <p>
356    * Note that this method never returns <jk>null</jk> and that {@link RequestHeader#isPresent()} can be used
357    * to test for the existence of the header.
358    *
359    * @param name The header name.  Must not be <jk>null</jk>.
360    * @return The header.  Never <jk>null</jk>.
361    */
362   public RequestHeader getFirst(String name) {
363      reqnn("name", name);
364      return stream(name).findFirst().orElseGet(() -> new RequestHeader(req, name, null).parser(parser));
365   }
366
367   /**
368    * Returns the last header with the specified name.
369    *
370    * <p>
371    * Note that this method never returns <jk>null</jk> and that {@link RequestHeader#isPresent()} can be used
372    * to test for the existence of the header.
373    *
374    * @param name The header name.  Must not be <jk>null</jk>.
375    * @return The header.  Never <jk>null</jk>.
376    */
377   public RequestHeader getLast(String name) {
378      reqnn("name", name);
379      var v = Holder.<RequestHeader>empty();
380      stream(name).forEach(v::set);
381      return v.orElseGet(() -> new RequestHeader(req, name, null).parser(parser));
382   }
383
384   /**
385    * Returns all the unique header names in this list.
386    * @return The list of all unique header names in this list.
387    *    <br>List is unmodifiable.
388    */
389   public List<String> getNames() { return stream().map(RequestHeader::getName).map(x -> caseSensitive ? x : lcr(x)).distinct().toList(); }
390
391   /**
392    * Returns all headers in sorted order.
393    *
394    * @return The stream of all headers in sorted order.
395    */
396   public Stream<RequestHeader> getSorted() {
397      Comparator<RequestHeader> x;
398      if (caseSensitive)
399         x = Comparator.comparing(RequestHeader::getName);
400      else
401         x = (x1, x2) -> String.CASE_INSENSITIVE_ORDER.compare(x1.getName(), x2.getName());
402      return stream().sorted(x);
403   }
404
405   /**
406    * Sets the parser to use for part values.
407    *
408    * @param value The new value for this setting.
409    * @return This object.
410    */
411   public RequestHeaderList parser(HttpPartParserSession value) {
412      parser = value;
413      forEach(x -> x.parser(parser));
414      return this;
415   }
416
417   /**
418    * Remove header by name.
419    *
420    * @param name The header names.  Must not be <jk>null</jk>.
421    * @return This object.
422    */
423   public RequestHeaderList remove(String name) {
424      reqnn("name", name);
425      removeIf(x -> eq(x.getName(), name));
426      return this;
427   }
428
429   /**
430    * Sets request header values.
431    *
432    * <p>
433    * Headers are added to the end.
434    * <br>Any previous headers with the same name are removed.
435    *
436    * @param headers The header to set.  Must not be <jk>null</jk> or contain <jk>null</jk>.
437    * @return This object.
438    */
439   public RequestHeaderList set(HttpHeader...headers) {
440      reqnn("headers", headers);
441      for (var h : headers)
442         remove(h.getName());
443      for (var h : headers)
444         add(h);
445      return this;
446   }
447
448   /**
449    * Sets a request header value.
450    *
451    * <p>
452    * Header is added to the end.
453    * <br>Any previous headers with the same name are removed.
454    *
455    * @param name The header name.  Must not be <jk>null</jk>.
456    * @param value
457    *    The header value.
458    *    <br>Converted to a string using {@link Object#toString()}.
459    *    <br>Can be <jk>null</jk> (the header is set with a <jk>null</jk> value).
460    * @return This object.
461    */
462   public RequestHeaderList set(String name, Object value) {
463      reqnn("name", name);
464      set(new RequestHeader(req, name, s(value)).parser(parser));
465      return this;
466   }
467
468   /**
469    * Returns all headers with the specified name.
470    *
471    * @param name The header name.
472    * @return The stream of all headers with matching names.  Never <jk>null</jk>.
473    */
474   public Stream<RequestHeader> stream(String name) {
475      return stream().filter(x -> eq(x.getName(), name));
476   }
477
478   /**
479    * Returns a copy of this object but only with the specified header names copied.
480    *
481    * @param names The list to include in the copy.
482    * @return A new list object.
483    */
484   public RequestHeaderList subset(String...names) {
485      return new RequestHeaderList(this, names);
486   }
487
488   protected FluentMap<String,Object> properties() {
489      // @formatter:off
490      var m = filteredBeanPropertyMap();
491      for (var n : getNames())
492         m.a(n, get(n).asString().orElse(null));
493      return m;
494      // @formatter:on
495   }
496
497   @Override
498   public boolean equals(Object o) {
499      return this == o || (o instanceof RequestHeaderList o2 && super.equals(o2));
500   }
501
502   @Override
503   public int hashCode() {
504      return super.hashCode();
505   }
506
507   @Override /* Overridden from Object */
508   public String toString() {
509      return r(properties());
510   }
511
512   private boolean eq(String s1, String s2) {
513      return Shorts.eq(! caseSensitive, s1, s2);  // NOAI
514   }
515
516   private String key(String name) {
517      return caseSensitive ? name : lcr(name);
518   }
519}