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}