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.client; 014 015import static org.apache.juneau.common.internal.ThrowableUtils.*; 016import static org.apache.juneau.httppart.HttpPartType.*; 017import static org.apache.juneau.internal.ClassUtils.*; 018import static org.apache.juneau.internal.CollectionUtils.*; 019 020import java.lang.reflect.*; 021import java.time.*; 022import java.util.*; 023import java.util.regex.*; 024 025import org.apache.http.*; 026import org.apache.juneau.*; 027import org.apache.juneau.assertions.*; 028import org.apache.juneau.http.header.*; 029import org.apache.juneau.httppart.*; 030import org.apache.juneau.oapi.*; 031import org.apache.juneau.parser.ParseException; 032import org.apache.juneau.reflect.*; 033import org.apache.juneau.rest.client.assertion.*; 034 035/** 036 * Represents a single header on an HTTP response. 037 * 038 * <p> 039 * An extension of an HttpClient {@link Header} that provides various support for converting the header to POJOs and 040 * other convenience methods. 041 * 042 * <h5 class='section'>See Also:</h5><ul> 043 * <li class='link'><a class="doclink" href="../../../../../index.html#juneau-rest-client">juneau-rest-client</a> 044 * </ul> 045 */ 046public class ResponseHeader extends BasicHeader { 047 048 private static final long serialVersionUID = 1L; 049 050 static final Header NULL_HEADER = new Header() { 051 052 @Override /* Header */ 053 public String getName() { 054 return null; 055 } 056 057 @Override /* Header */ 058 public String getValue() { 059 return null; 060 } 061 062 @Override /* Header */ 063 public HeaderElement[] getElements() throws org.apache.http.ParseException { 064 return new HeaderElement[0]; 065 } 066 }; 067 068 private final HeaderElement[] elements; 069 private final RestRequest request; 070 private final RestResponse response; 071 private HttpPartParserSession parser; 072 private HttpPartSchema schema; 073 074 /** 075 * Constructor. 076 * @param name The header name. 077 * @param request The request object. 078 * @param response The response object. 079 * @param header The wrapped header. Can be <jk>null</jk>. 080 * @throws IllegalArgumentException If name is <jk>null</jk> or empty. 081 */ 082 public ResponseHeader(String name, RestRequest request, RestResponse response, Header header) { 083 super(name, header == null ? null : header.getValue()); 084 this.request = request; 085 this.response = response; 086 this.elements = header == null ? new HeaderElement[0] : header.getElements(); 087 parser(null); 088 } 089 090 //------------------------------------------------------------------------------------------------------------------ 091 // Setters 092 //------------------------------------------------------------------------------------------------------------------ 093 094 /** 095 * Specifies the part schema for this header. 096 * 097 * <p> 098 * Used by schema-based part parsers such as {@link OpenApiParser}. 099 * 100 * @param value 101 * The part schema. 102 * @return This object. 103 */ 104 public ResponseHeader schema(HttpPartSchema value) { 105 this.schema = value; 106 return this; 107 } 108 109 /** 110 * Specifies the part parser to use for this header. 111 * 112 * <p> 113 * If not specified, uses the part parser defined on the client by calling {@link RestClient.Builder#partParser(Class)}. 114 * 115 * @param value 116 * The new part parser to use for this header. 117 * <br>If <jk>null</jk>, {@link SimplePartParser#DEFAULT} will be used. 118 * @return This object. 119 */ 120 public ResponseHeader parser(HttpPartParserSession value) { 121 this.parser = value == null ? SimplePartParser.DEFAULT_SESSION : value; 122 return this; 123 } 124 125 //------------------------------------------------------------------------------------------------------------------ 126 // Retrievers 127 //------------------------------------------------------------------------------------------------------------------ 128 129 /** 130 * Returns the value of this header as an integer. 131 * 132 * @return The value of this header as an integer, or {@link Optional#empty()} if the header was not present. 133 */ 134 public Optional<Integer> asInteger() { 135 return asIntegerHeader().asInteger(); 136 } 137 138 /** 139 * Returns the value of this header as a boolean. 140 * 141 * @return The value of this header as a boolean, or {@link Optional#empty()} if the header was not present. 142 */ 143 public Optional<Boolean> asBoolean() { 144 return asBooleanHeader().asBoolean(); 145 } 146 147 /** 148 * Returns the value of this header as a long. 149 * 150 * @return The value of this header as a long, or {@link Optional#empty()} if the header was not present. 151 */ 152 public Optional<Long> asLong() { 153 return asLongHeader().asLong(); 154 } 155 156 /** 157 * Returns the value of this header as a date. 158 * 159 * @return The value of this header as a date, or {@link Optional#empty()} if the header was not present. 160 */ 161 public Optional<ZonedDateTime> asDate() { 162 return asDateHeader().asZonedDateTime(); 163 } 164 165 /** 166 * Returns the value of this header as a list from a comma-delimited string. 167 * 168 * @return The value of this header as a list from a comma-delimited string, or {@link Optional#empty()} if the header was not present. 169 */ 170 public Optional<String[]> asCsvArray() { 171 return asCsvHeader().asArray(); 172 } 173 174 /** 175 * Returns the value of this header as a {@link BasicHeader}. 176 * 177 * @param c The subclass of {@link BasicHeader} to instantiate. 178 * @param <T> The subclass of {@link BasicHeader} to instantiate. 179 * @return The value of this header as a string, never <jk>null</jk>. 180 */ 181 public <T extends BasicHeader> T asHeader(Class<T> c) { 182 try { 183 ClassInfo ci = ClassInfo.of(c); 184 ConstructorInfo cc = ci.getPublicConstructor(x -> x.hasParamTypes(String.class)); 185 if (cc != null) 186 return cc.invoke(getValue()); 187 cc = ci.getPublicConstructor(x -> x.hasParamTypes(String.class, String.class)); 188 if (cc != null) 189 return cc.invoke(getName(), getValue()); 190 } catch (Throwable e) { 191 if (e instanceof ExecutableException) 192 e = ((ExecutableException)e).getCause(); 193 throw asRuntimeException(e); 194 } 195 throw new BasicRuntimeException("Could not determine a method to construct type {0}", className(c)); 196 } 197 198 /** 199 * Returns the value of this header as a CSV array header. 200 * 201 * @return The value of this header as a CSV array header, never <jk>null</jk>. 202 */ 203 public BasicCsvHeader asCsvHeader() { 204 return new BasicCsvHeader(getName(), getValue()); 205 } 206 207 /** 208 * Returns the value of this header as a date header. 209 * 210 * @return The value of this header as a date header, never <jk>null</jk>. 211 */ 212 public BasicDateHeader asDateHeader() { 213 return new BasicDateHeader(getName(), getValue()); 214 } 215 216 /** 217 * Returns the value of this header as an entity validator array header. 218 * 219 * @return The value of this header as an entity validator array header, never <jk>null</jk>. 220 */ 221 public BasicEntityTagsHeader asEntityTagsHeader() { 222 return new BasicEntityTagsHeader(getName(), getValue()); 223 } 224 225 /** 226 * Returns the value of this header as an entity validator header. 227 * 228 * @return The value of this header as an entity validator array, never <jk>null</jk>. 229 */ 230 public BasicEntityTagHeader asEntityTagHeader() { 231 return new BasicEntityTagHeader(getName(), getValue()); 232 } 233 234 /** 235 * Returns the value of this header as an integer header. 236 * 237 * @return The value of this header as an integer header, never <jk>null</jk>. 238 */ 239 public BasicIntegerHeader asIntegerHeader() { 240 return new BasicIntegerHeader(getName(), getValue()); 241 } 242 243 /** 244 * Returns the value of this header as an boolean header. 245 * 246 * @return The value of this header as an boolean header, never <jk>null</jk>. 247 */ 248 public BasicBooleanHeader asBooleanHeader() { 249 return new BasicBooleanHeader(getName(), getValue()); 250 } 251 252 /** 253 * Returns the value of this header as a long header. 254 * 255 * @return The value of this header as a long header, never <jk>null</jk>. 256 */ 257 public BasicLongHeader asLongHeader() { 258 return new BasicLongHeader(getName(), getValue()); 259 } 260 261 /** 262 * Returns the value of this header as a range array header. 263 * 264 * @return The value of this header as a range array header, never <jk>null</jk>. 265 */ 266 public BasicStringRangesHeader asStringRangesHeader() { 267 return new BasicStringRangesHeader(getName(), getValue()); 268 } 269 270 /** 271 * Returns the value of this header as a string header. 272 * 273 * @return The value of this header as a string header, never <jk>null</jk>. 274 */ 275 public BasicStringHeader asStringHeader() { 276 return new BasicStringHeader(getName(), getValue()); 277 } 278 279 /** 280 * Returns the value of this header as a URI header. 281 * 282 * @return The value of this header as a URI header, never <jk>null</jk>. 283 */ 284 public BasicUriHeader asUriHeader() { 285 return new BasicUriHeader(getName(), getValue()); 286 } 287 288 /** 289 * Same as {@link #asString()} but sets the value in a mutable for fluent calls. 290 * 291 * @param value The mutable to set the header value in. 292 * @return This object. 293 */ 294 public RestResponse asString(Value<String> value) { 295 value.set(orElse(null)); 296 return response; 297 } 298 299 /** 300 * Converts this header to the specified type. 301 * 302 * <p> 303 * See <a class="doclink" href="../../../../../index.html#jm.ComplexDataTypes">Complex Data Types</a> for information on defining complex generic types of {@link Map Maps} and {@link Collection Collections}. 304 * 305 * @param <T> The type to convert to. 306 * @param type The type to convert to. 307 * @param args The type parameters. 308 * @return The converted type, or <jk>null</jk> if header is not present. 309 */ 310 public <T> Optional<T> as(Type type, Type...args) { 311 return as(request.getClassMeta(type, args)); 312 } 313 314 /** 315 * Same as {@link #as(Type,Type...)} but sets the value in a mutable for fluent calls. 316 * 317 * <p> 318 * See <a class="doclink" href="../../../../../index.html#jm.ComplexDataTypes">Complex Data Types</a> for information on defining complex generic types of {@link Map Maps} and {@link Collection Collections}. 319 * 320 * @param value The mutable to set the parsed header value in. 321 * @param <T> The type to convert to. 322 * @param type The type to convert to. 323 * @param args The type parameters. 324 * @return This object. 325 */ 326 @SuppressWarnings("unchecked") 327 public <T> RestResponse as(Value<T> value, Type type, Type...args) { 328 value.set((T)as(type, args).orElse(null)); 329 return response; 330 } 331 332 /** 333 * Converts this header to the specified type. 334 * 335 * @param <T> The type to convert to. 336 * @param type The type to convert to. 337 * @return The converted type, or <jk>null</jk> if header is not present. 338 */ 339 public <T> Optional<T> as(Class<T> type) { 340 return as(request.getClassMeta(type)); 341 } 342 343 /** 344 * Same as {@link #as(Class)} but sets the value in a mutable for fluent calls. 345 * 346 * @param value The mutable to set the parsed header value in. 347 * @param <T> The type to convert to. 348 * @param type The type to convert to. 349 * @return This object. 350 */ 351 public <T> RestResponse as(Value<T> value, Class<T> type) { 352 value.set(as(type).orElse(null)); 353 return response; 354 } 355 356 /** 357 * Converts this header to the specified type. 358 * 359 * @param <T> The type to convert to. 360 * @param type The type to convert to. 361 * @return The converted type, or <jk>null</jk> if header is not present. 362 */ 363 public <T> Optional<T> as(ClassMeta<T> type) { 364 try { 365 return optional(parser.parse(HEADER, schema, getValue(), type)); 366 } catch (ParseException e) { 367 throw new BasicRuntimeException(e, "Could not parse response header {0}.", getName()); 368 } 369 } 370 371 /** 372 * Same as {@link #as(ClassMeta)} but sets the value in a mutable for fluent calls. 373 * 374 * @param value The mutable to set the parsed header value in. 375 * @param <T> The type to convert to. 376 * @param type The type to convert to. 377 * @return This object. 378 */ 379 public <T> RestResponse as(Value<T> value, ClassMeta<T> type) { 380 value.set(as(type).orElse(null)); 381 return response; 382 } 383 384 /** 385 * Matches the specified pattern against this header value. 386 * 387 * <h5 class='section'>Example:</h5> 388 * <p class='bjava'> 389 * <jc>// Parse header using a regular expression.</jc> 390 * Matcher <jv>matcher</jv> = <jv>client</jv> 391 * .get(<jsf>URI</jsf>) 392 * .run() 393 * .getResponseHeader(<js>"Content-Type"</js>).asMatcher(Pattern.<jsm>compile</jsm>(<js>"application/(.*)"</js>)); 394 * 395 * <jk>if</jk> (<jv>matcher</jv>.matches()) { 396 * String <jv>mediaType</jv> = <jv>matcher</jv>.group(1); 397 * } 398 * </p> 399 * 400 * @param pattern The regular expression pattern to match. 401 * @return The matcher. 402 */ 403 public Matcher asMatcher(Pattern pattern) { 404 return pattern.matcher(orElse("")); 405 } 406 407 /** 408 * Matches the specified pattern against this header value. 409 * 410 * <h5 class='section'>Example:</h5> 411 * <p class='bjava'> 412 * <jc>// Parse header using a regular expression.</jc> 413 * Matcher <jv>matcher</jv> = <jv>client</jv> 414 * .get(<jsf>URI</jsf>) 415 * .run() 416 * .getHeader(<js>"Content-Type"</js>).asMatcher(<js>"application/(.*)"</js>); 417 * 418 * <jk>if</jk> (<jv>matcher</jv>.matches()) { 419 * String <jv>mediaType</jv> = <jv>matcher</jv>.group(1); 420 * } 421 * </p> 422 * 423 * @param regex The regular expression pattern to match. 424 * @return The matcher. 425 */ 426 public Matcher asMatcher(String regex) { 427 return asMatcher(regex, 0); 428 } 429 430 /** 431 * Matches the specified pattern against this header value. 432 * 433 * <h5 class='section'>Example:</h5> 434 * <p class='bjava'> 435 * <jc>// Parse header using a regular expression.</jc> 436 * Matcher <jv>matcher</jv> = <jv>client</jv> 437 * .get(<jsf>URI</jsf>) 438 * .run() 439 * .getHeader(<js>"Content-Type"</js>).asMatcher(<js>"application/(.*)"</js>, <jsf>CASE_INSENSITIVE</jsf>); 440 * 441 * <jk>if</jk> (<jv>matcher</jv>.matches()) { 442 * String <jv>mediaType</jv> = <jv>matcher</jv>.group(1); 443 * } 444 * </p> 445 * 446 * @param regex The regular expression pattern to match. 447 * @param flags Pattern match flags. See {@link Pattern#compile(String, int)}. 448 * @return The matcher. 449 */ 450 public Matcher asMatcher(String regex, int flags) { 451 return asMatcher(Pattern.compile(regex, flags)); 452 } 453 454 //------------------------------------------------------------------------------------------------------------------ 455 // Assertions 456 //------------------------------------------------------------------------------------------------------------------ 457 458 /** 459 * Provides the ability to perform fluent-style assertions on this response header. 460 * 461 * <h5 class='section'>Examples:</h5> 462 * <p class='bjava'> 463 * <jc>// Validates the content type header is provided.</jc> 464 * <jv>client</jv> 465 * .get(<jsf>URI</jsf>) 466 * .run() 467 * .getHeader(<js>"Content-Type"</js>).assertValue().exists(); 468 * 469 * <jc>// Validates the content type is JSON.</jc> 470 * <jv>client</jv> 471 * .get(<jsf>URI</jsf>) 472 * .run() 473 * .getHeader(<js>"Content-Type"</js>).assertValue().equals(<js>"application/json"</js>); 474 * 475 * <jc>// Validates the content type is JSON using test predicate.</jc> 476 * <jv>client</jv> 477 * .get(<jsf>URI</jsf>) 478 * .run() 479 * .getHeader(<js>"Content-Type"</js>).assertValue().is(<jv>x</jv> -> <jv>x</jv>.equals(<js>"application/json"</js>)); 480 * 481 * <jc>// Validates the content type is JSON by just checking for substring.</jc> 482 * <jv>client</jv> 483 * .get(<jsf>URI</jsf>) 484 * .run() 485 * .getHeader(<js>"Content-Type"</js>).assertValue().contains(<js>"json"</js>); 486 * 487 * <jc>// Validates the content type is JSON using regular expression.</jc> 488 * <jv>client</jv> 489 * .get(<jsf>URI</jsf>) 490 * .run() 491 * .getHeader(<js>"Content-Type"</js>).assertValue().isPattern(<js>".*json.*"</js>); 492 * 493 * <jc>// Validates the content type is JSON using case-insensitive regular expression.</jc> 494 * <jv>client</jv> 495 * .get(<jsf>URI</jsf>) 496 * .run() 497 * .getHeader(<js>"Content-Type"</js>).assertValue().isPattern(<js>".*json.*"</js>, <jsf>CASE_INSENSITIVE</jsf>); 498 * </p> 499 * 500 * <p> 501 * The assertion test returns the original response object allowing you to chain multiple requests like so: 502 * <p class='bjava'> 503 * <jc>// Validates the header and converts it to a bean.</jc> 504 * MediaType <jv>mediaType</jv> = <jv>client</jv> 505 * .get(<jsf>URI</jsf>) 506 * .run() 507 * .getHeader(<js>"Content-Type"</js>).assertValue().isNotEmpty() 508 * .getHeader(<js>"Content-Type"</js>).assertValue().isPattern(<js>".*json.*"</js>) 509 * .getHeader(<js>"Content-Type"</js>).as(MediaType.<jk>class</jk>); 510 * </p> 511 * 512 * @return A new fluent assertion object. 513 */ 514 public FluentResponseHeaderAssertion<ResponseHeader> assertValue() { 515 return new FluentResponseHeaderAssertion<>(this, this); 516 } 517 518 /** 519 * Shortcut for calling <c>assertValue().asString()</c>. 520 * 521 * @return A new fluent assertion. 522 */ 523 public FluentStringAssertion<ResponseHeader> assertString() { 524 return new FluentResponseHeaderAssertion<>(this, this).asString(); 525 } 526 527 /** 528 * Shortcut for calling <c>assertValue().asInteger()</c>. 529 * 530 * @return A new fluent assertion. 531 */ 532 public FluentIntegerAssertion<ResponseHeader> assertInteger() { 533 return new FluentResponseHeaderAssertion<>(this, this).asInteger(); 534 } 535 536 /** 537 * Shortcut for calling <c>assertValue().asLong()</c>. 538 * 539 * @return A new fluent assertion. 540 */ 541 public FluentLongAssertion<ResponseHeader> assertLong() { 542 return new FluentResponseHeaderAssertion<>(this, this).asLong(); 543 } 544 545 /** 546 * Shortcut for calling <c>assertValue().asZonedDateTime()</c>. 547 * 548 * @return A new fluent assertion. 549 */ 550 public FluentZonedDateTimeAssertion<ResponseHeader> assertZonedDateTime() { 551 return new FluentResponseHeaderAssertion<>(this, this).asZonedDateTime(); 552 } 553 554 /** 555 * Returns the response that created this object. 556 * 557 * @return The response that created this object. 558 */ 559 public RestResponse response() { 560 return response; 561 } 562 563 //------------------------------------------------------------------------------------------------------------------ 564 // Header passthrough methods. 565 //------------------------------------------------------------------------------------------------------------------ 566 567 /** 568 * Parses the value. 569 * 570 * @return An array of {@link HeaderElement} entries, may be empty, but is never <jk>null</jk>. 571 * @throws org.apache.http.ParseException In case of a parsing error. 572 */ 573 @Override /* Header */ 574 public HeaderElement[] getElements() throws org.apache.http.ParseException { 575 return elements; 576 } 577}