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