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