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