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.http.classic.response; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021import static org.apache.juneau.http.classic.HttpEntities.*; 022import static org.apache.juneau.http.classic.HttpHeaders.*; 023 024import java.lang.reflect.*; 025import java.util.*; 026 027import org.apache.http.*; 028import org.apache.http.impl.*; 029import org.apache.http.params.*; 030import org.apache.juneau.http.UnmodifiableBean; 031import org.apache.juneau.http.classic.*; 032import org.apache.juneau.http.classic.header.*; 033import org.apache.juneau.marshall.*; 034 035/** 036 * Basic implementation of the {@link HttpResponse} interface for error responses. 037 * 038 * <p> 039 * Although this class implements the various setters defined on the {@link HttpResponse} interface, it's in general 040 * going to be more efficient to set the status/headers/content of this bean through the builder. 041 * 042 * <p> 043 * Immutability is expressed with the "funnel + nested {@code Unmodifiable} snapshot" paradigm: every mutator (fluent 044 * setter and {@code void} interface mutator) routes through the single protected {@link #modify(Runnable)} choke-point, 045 * and each concrete leaf gets a nested {@code X.Unmodifiable} whose only behavioral override is a throwing 046 * {@code modify(...)}. A leaf's {@link #unmodifiable()} factory returns a point-in-time snapshot of type 047 * {@code X.Unmodifiable}; any attempt to set a property on it throws an {@link UnsupportedOperationException}. 048 * 049 * <p> 050 * This is a {@link Throwable}-rooted hierarchy, so (unlike {@link BasicHttpResponse}) it cannot be a self-typed (CRTP) 051 * root — JLS §8.1.2 forbids a generic {@code Throwable}. The funnel therefore returns the base type and each leaf 052 * keeps its covariant setter overrides. 053 * 054 * <h5 class='section'>Notes:</h5><ul> 055 * <li class='warn'>Beans are not thread safe unless they're marked as unmodifiable. 056 * </ul> 057 * 058 * <h5 class='section'>See Also:</h5><ul> 059 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a> 060 * </ul> 061 * 062 * @serial exclude 063 */ 064@Marshalled(as=MarshalledAs.STRING) 065@SuppressWarnings({ 066 "deprecation", // Uses deprecated HttpMessage API 067 "java:S110", // Deep exception-hierarchy inheritance is structural and intentional; flattening would break the HTTP-response exception model. 068 "java:S1104", // Package-private headers/statusLine/content fields are reassigned by setters and subclass constructors, so they cannot be final or private 069 "java:S1165", // headers/statusLine/content are replaced after construction by setters, so the exception's fields cannot be final 070 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 071}) 072public class BasicHttpException extends BasicRuntimeException implements HttpResponse { 073 074 private static final long serialVersionUID = 1L; 075 076 HeaderList headers = HeaderList.create(); 077 transient BasicStatusLine statusLine = new BasicStatusLine(); 078 transient HttpEntity content; 079 080 /** 081 * Constructor. 082 */ 083 public BasicHttpException() { 084 super((Throwable)null); 085 } 086 087 /** 088 * Constructor. 089 * 090 * <p> 091 * This is the constructor used when parsing an HTTP response. 092 * 093 * @param response The HTTP response being parsed. Must not be <jk>null</jk>. 094 */ 095 public BasicHttpException(HttpResponse response) { 096 super((Throwable)null); 097 reqnn("response", response); 098 var h = response.getLastHeader("Thrown"); 099 if (nn(h)) { 100 var partsOpt = thrown(h.getValue()).asParts(); 101 if (partsOpt.isPresent() && !partsOpt.get().isEmpty()) 102 setMessage(partsOpt.get().get(0).getMessage()); 103 } 104 setHeaders(response.getAllHeaders()); 105 setContent(response.getEntity()); 106 setStatusCode(response.getStatusLine().getStatusCode()); 107 } 108 109 /** 110 * Constructor. 111 * 112 * @param statusCode The HTTP status code. 113 */ 114 public BasicHttpException(int statusCode) { 115 super((Throwable)null); 116 setStatusCode(statusCode); 117 } 118 119 /** 120 * Constructor. 121 * 122 * @param statusCode The HTTP status code. 123 * @param msg The message. Can be <jk>null</jk>, in which case the reason phrase is used as the message. 124 * @param args Optional <c>printf</c>-style (<js>"%s"</js>) arguments in the message. 125 */ 126 public BasicHttpException(int statusCode, String msg, Object...args) { 127 super(msg, args); 128 setStatusCode(statusCode); 129 } 130 131 /** 132 * Constructor. 133 * 134 * @param statusCode The HTTP status code. 135 * @param causedBy The cause. Can be <jk>null</jk>, in which case no cause is chained and the reason phrase is used as the message. 136 */ 137 public BasicHttpException(int statusCode, Throwable causedBy) { 138 super(causedBy); 139 setStatusCode(statusCode); 140 } 141 142 /** 143 * Constructor. 144 * 145 * @param statusCode The HTTP status code. 146 * @param cause The caused-by exception. Can be <jk>null</jk> (no cause is chained). 147 * @param msg The message. Can be <jk>null</jk>, in which case the cause's message is used, or the reason phrase if the cause is also <jk>null</jk> or has no message. 148 * @param args The message arguments. 149 */ 150 public BasicHttpException(int statusCode, Throwable cause, String msg, Object...args) { 151 super(cause, msg, args); 152 setStatusCode(statusCode); 153 setContent(f(msg, args)); 154 } 155 156 /** 157 * Copy constructor. 158 * 159 * <p> 160 * The mutable sub-beans are deep-copied by <b>direct field assignment</b>, never through a funneled setter. This is 161 * the snapshot path used by {@link #unmodifiable()}: the constructor runs inside an {@code Unmodifiable} instance 162 * whose {@link #modify(Runnable)} already throws, so a setter-based copy would blow up during construction. 163 * 164 * @param copyFrom The bean to copy. Must not be <jk>null</jk>. 165 */ 166 protected BasicHttpException(BasicHttpException copyFrom) { 167 super(copyFrom.getCause(), "%s", copyFrom.getMessage()); 168 statusLine = copyFrom.statusLine.copy(); 169 headers = copyFrom.headers.copy(); 170 content = copyFrom.content; 171 } 172 173 @Override /* Overridden from HttpMessage */ 174 public void addHeader(Header value) { 175 modify(() -> headers.append(value)); 176 } 177 178 @Override /* Overridden from HttpMessage */ 179 public void addHeader(String name, String value) { 180 modify(() -> headers.append(name, value)); 181 } 182 183 @Override /* Overridden from HttpMessage */ 184 public boolean containsHeader(String name) { 185 return headers.contains(name); 186 } 187 188 @Override /* Overridden from HttpMessage */ 189 public Header[] getAllHeaders() { return headers.getAll(); } 190 191 @Override /* Overridden from HttpMessage */ 192 public HttpEntity getEntity() { 193 // Constructing a StringEntity is somewhat expensive, so don't create it unless it's needed. 194 if (content == null) 195 content = stringEntity(getMessage()); 196 return content; 197 } 198 199 @Override /* Overridden from HttpMessage */ 200 public Header getFirstHeader(String name) { 201 return headers.getFirst(name).orElse(null); 202 } 203 204 /** 205 * Returns all error messages from all errors in this stack. 206 * 207 * <p> 208 * Typically useful if you want to render all the error messages in the stack, but don't want to render all the 209 * stack traces too. 210 * 211 * @param scrubForXssVulnerabilities 212 * If <jk>true</jk>, replaces <js>'<'</js>, <js>'>'</js>, and <js>'&'</js> characters with spaces. 213 * @return All error messages from all errors in this stack. 214 */ 215 public String getFullStackMessage(boolean scrubForXssVulnerabilities) { 216 var msg = getMessage(); 217 StringBuilder sb = new StringBuilder(); 218 if (nn(msg)) { 219 if (scrubForXssVulnerabilities) 220 msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' '); 221 sb.append(msg); 222 } 223 Throwable e = getCause(); 224 while (nn(e)) { 225 msg = e.getMessage(); 226 if (nn(msg) && scrubForXssVulnerabilities) 227 msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' '); 228 var cls = cns(e); 229 if (msg == null) 230 sb.append(f("\nCaused by (%s)", cls)); 231 else 232 sb.append(f("\nCaused by (%s): %s", cls, msg)); 233 e = e.getCause(); 234 } 235 return sb.toString(); 236 } 237 238 /** 239 * Returns access to the underlying builder for the headers. 240 * 241 * @return The underlying builder for the headers. 242 */ 243 public HeaderList getHeaders() { 244 return headers; 245 } 246 247 @Override /* Overridden from HttpMessage */ 248 public Header[] getHeaders(String name) { 249 return headers.getAll(name); 250 } 251 252 @Override /* Overridden from HttpMessage */ 253 public Header getLastHeader(String name) { 254 return headers.getLast(name).orElse(null); 255 } 256 257 @Override /* Overridden from HttpMessage */ 258 public Locale getLocale() { return statusLine.getLocale(); } 259 260 @Override /* Overridden from Throwable */ 261 public String getMessage() { 262 String m = super.getMessage(); 263 if (m == null && nn(getCause())) 264 m = getCause().getMessage(); 265 if (m == null) 266 m = statusLine.getReasonPhrase(); 267 return m; 268 } 269 270 @Override /* Overridden from HttpMessage */ 271 public HttpParams getParams() { return null; } 272 273 @Override /* Overridden from HttpMessage */ 274 public ProtocolVersion getProtocolVersion() { return statusLine.getProtocolVersion(); } 275 276 /** 277 * Returns the root cause of this exception. 278 * 279 * <p> 280 * The root cause is the first exception in the init-cause parent chain that's not one of the following: 281 * <ul> 282 * <li>{@link BasicHttpException} 283 * <li>{@link InvocationTargetException} 284 * </ul> 285 * 286 * @return The root cause of this exception, or <jk>null</jk> if no root cause was found. 287 */ 288 public Throwable getRootCause() { 289 Throwable t = this; 290 while (nn(t)) { 291 if (! (t instanceof BasicHttpException || t instanceof InvocationTargetException)) 292 return t; 293 t = t.getCause(); 294 } 295 return null; 296 } 297 298 @Override /* Overridden from HttpMessage */ 299 public StatusLine getStatusLine() { return statusLine; } 300 301 @Override /* Overridden from Object */ 302 public boolean equals(Object o) { 303 // Content equality. The 'content' HttpEntity is deliberately excluded: it is a wrapper without value 304 // semantics (two StringEntity instances of the same text are not equal), and it is derived from the message 305 // (getEntity() lazily builds stringEntity(getMessage())), which is already compared here. 306 return o instanceof BasicHttpException other && eq(this, other, (x, y) -> 307 eq(x.statusLine, y.statusLine) && eq(x.headers, y.headers) && eq(x.getMessage(), y.getMessage())); 308 } 309 310 @Override /* Overridden from Object */ 311 public int hashCode() { 312 return h(statusLine, headers, getMessage()); 313 } 314 315 @Override /* Overridden from HttpMessage */ 316 public HeaderIterator headerIterator() { 317 return headers.headerIterator(); 318 } 319 320 @Override /* Overridden from HttpMessage */ 321 public HeaderIterator headerIterator(String name) { 322 return headers.headerIterator(name); 323 } 324 325 /** 326 * Returns <jk>true</jk> if this bean is unmodifiable. 327 * 328 * @return <jk>true</jk> if this bean is an {@link UnmodifiableBean} snapshot. 329 */ 330 public boolean isUnmodifiable() { return this instanceof UnmodifiableBean; } 331 332 @Override /* Overridden from HttpMessage */ 333 public void removeHeader(Header value) { 334 modify(() -> headers.remove(value)); 335 } 336 337 @Override /* Overridden from HttpMessage */ 338 public void removeHeaders(String name) { 339 modify(() -> headers.remove(name)); 340 } 341 342 /** 343 * Sets the body on this response. 344 * 345 * @param value The body on this response. Can be <jk>null</jk> to unset the property. 346 * @return This object. 347 */ 348 public BasicHttpException setContent(HttpEntity value) { 349 return modify(() -> content = value); 350 } 351 352 /** 353 * Sets the body on this response. 354 * 355 * @param value The body on this response. Can be <jk>null</jk> (treated as an empty string). 356 * @return This object. 357 */ 358 public BasicHttpException setContent(String value) { 359 return modify(() -> content = stringEntity(value)); 360 } 361 362 @Override /* Overridden from HttpMessage */ 363 public void setEntity(HttpEntity entity) { 364 modify(() -> content = entity); 365 } 366 367 @Override /* Overridden from HttpMessage */ 368 public void setHeader(Header value) { 369 modify(() -> headers.set(value)); 370 } 371 372 @Override /* Overridden from HttpMessage */ 373 public void setHeader(String name, String value) { 374 modify(() -> headers.set(name, value)); 375 } 376 377 /** 378 * Sets a header on this response. 379 * 380 * @param name The header name. 381 * @param value The header value. 382 * @return This object. 383 */ 384 public BasicHttpException setHeader2(String name, Object value) { 385 return modify(() -> headers.set(name, value)); 386 } 387 388 @Override /* Overridden from HttpMessage */ 389 public void setHeaders(Header[] values) { 390 modify(() -> headers.removeAll().append(values)); 391 } 392 393 /** 394 * Sets the specified headers on this response. 395 * 396 * @param value The new value. Must not be <jk>null</jk>. 397 * @return This object. 398 */ 399 public BasicHttpException setHeaders(HeaderList value) { 400 return modify(() -> headers = value.copy()); 401 } 402 403 /** 404 * Sets the specified headers on this response. 405 * 406 * @param values The headers to set. <jk>null</jk> values are ignored. 407 * @return This object. 408 */ 409 public BasicHttpException setHeaders(List<Header> values) { 410 return modify(() -> headers.set(values)); 411 } 412 413 /** 414 * Sets multiple headers on this response. 415 * 416 * @param values The headers to add. 417 * @return This object. 418 */ 419 public BasicHttpException setHeaders2(Header...values) { 420 return modify(() -> headers.set(values)); 421 } 422 423 @Override /* Overridden from HttpMessage */ 424 public void setLocale(Locale loc) { 425 modify(() -> statusLine.setLocale(loc)); 426 } 427 428 /** 429 * Sets the locale used to retrieve reason phrases. 430 * 431 * <p> 432 * If not specified, uses {@link Locale#getDefault()}. 433 * 434 * @param value The new value. Can be <jk>null</jk>. The effect is implementation-dependent, depending on the configured {@link ReasonPhraseCatalog} (e.g. ignored by {@link EnglishReasonPhraseCatalog}). 435 * @return This object. 436 */ 437 public BasicHttpException setLocale2(Locale value) { 438 return modify(() -> statusLine.setLocale(value)); 439 } 440 441 @Override /* Overridden from BasicRuntimeException */ 442 public BasicHttpException setMessage(String message, Object...args) { 443 return modify(() -> super.setMessage(message, args)); 444 } 445 446 @Override /* Overridden from HttpMessage */ 447 public void setParams(HttpParams params) { 448 // Deprecated optional interface method; routed through the funnel so it is frozen on unmodifiable snapshots. 449 modify(() -> { /* No-op */ }); 450 } 451 452 /** 453 * Sets the protocol version on the status line. 454 * 455 * <p> 456 * If not specified, <js>"HTTP/1.1"</js> will be used. 457 * 458 * @param value The new value. Can be <jk>null</jk>, in which case the default protocol version is cleared and {@link BasicStatusLine#getProtocolVersion()} returns <jk>null</jk>. 459 * @return This object. 460 */ 461 public BasicHttpException setProtocolVersion(ProtocolVersion value) { 462 return modify(() -> statusLine.setProtocolVersion(value)); 463 } 464 465 @Override /* Overridden from HttpMessage */ 466 public void setReasonPhrase(String reason) throws IllegalStateException { 467 modify(() -> statusLine.setReasonPhrase(reason)); 468 } 469 470 /** 471 * Sets the reason phrase on the status line. 472 * 473 * <p> 474 * If not specified, the reason phrase will be retrieved from the reason phrase catalog 475 * using the locale on this builder. 476 * 477 * @param value The new value. Can be <jk>null</jk> to fall back to the reason phrase catalog. 478 * @return This object. 479 */ 480 public BasicHttpException setReasonPhrase2(String value) { 481 return modify(() -> statusLine.setReasonPhrase(value)); 482 } 483 484 /** 485 * Sets the reason phrase catalog used to retrieve reason phrases. 486 * 487 * <p> 488 * If not specified, uses {@link EnglishReasonPhraseCatalog}. 489 * 490 * @param value The new value. Can be <jk>null</jk> to fall back to {@link EnglishReasonPhraseCatalog#INSTANCE}. 491 * @return This object. 492 */ 493 public BasicHttpException setReasonPhraseCatalog(ReasonPhraseCatalog value) { 494 return modify(() -> statusLine.setReasonPhraseCatalog(value)); 495 } 496 497 @Override /* Overridden from HttpMessage */ 498 public void setStatusCode(int code) throws IllegalStateException { 499 modify(() -> statusLine.setStatusCode(code)); 500 } 501 502 /** 503 * Same as {@link #setStatusCode(int)} but returns this object. 504 * 505 * @param code The new status code. 506 * @return This object. 507 * @throws IllegalStateException If status code could not be set. 508 */ 509 public BasicHttpException setStatusCode2(int code) throws IllegalStateException { 510 return modify(() -> statusLine.setStatusCode(code)); 511 } 512 513 /** 514 * Sets the protocol version on the status line. 515 * 516 * <p> 517 * If not specified, <js>"HTTP/1.1"</js> will be used. 518 * 519 * @param value The new value. Must not be <jk>null</jk>. 520 * @return This object. 521 */ 522 public BasicHttpException setStatusLine(BasicStatusLine value) { 523 return modify(() -> statusLine = value.copy()); 524 } 525 526 @Override /* Overridden from HttpMessage */ 527 public void setStatusLine(ProtocolVersion ver, int code) { 528 modify(() -> statusLine.setProtocolVersion(ver).setStatusCode(code)); 529 } 530 531 @Override /* Overridden from HttpMessage */ 532 public void setStatusLine(ProtocolVersion ver, int code, String reason) { 533 modify(() -> statusLine.setProtocolVersion(ver).setReasonPhrase(reason).setStatusCode(code)); 534 } 535 536 @Override /* Overridden from HttpMessage */ 537 public void setStatusLine(StatusLine value) { 538 modify(() -> statusLine.setProtocolVersion(value.getProtocolVersion()).setReasonPhrase(value.getReasonPhrase()).setStatusCode(value.getStatusCode())); 539 } 540 541 /** 542 * Returns an unmodifiable snapshot of this bean. 543 * 544 * <p> 545 * The returned instance is a point-in-time copy of the leaf's nested {@code Unmodifiable} type; any attempt to set a 546 * property on it throws an {@link UnsupportedOperationException}. This method is idempotent: if this bean is already 547 * unmodifiable it returns itself rather than taking another snapshot. The receiver is left unchanged (and still 548 * modifiable unless it was already unmodifiable). 549 * 550 * @return An unmodifiable snapshot of this bean, or this bean if it is already unmodifiable. 551 */ 552 public BasicHttpException unmodifiable() { 553 return this instanceof UnmodifiableBean ? this : new Unmodifiable(this); 554 } 555 556 @Override /* Overridden from Object */ 557 public String toString() { 558 return emptyIfNull(getLocalizedMessage()); 559 } 560 561 /** 562 * Verifies that the specified HTTP response has the expected status code and returns it. 563 * 564 * <p> 565 * Used by the <c>Xxx(HttpResponse)</c> constructors of the concrete response classes to validate the incoming 566 * response <i>before</i> its status line is copied. 567 * 568 * @param response The HTTP response to check. Must not be <jk>null</jk>. 569 * @param expected The status code the response is required to have. 570 * @return The same response. 571 * @throws IllegalArgumentException If the response status code is not what was expected. 572 */ 573 static HttpResponse requireStatus(HttpResponse response, int expected) throws IllegalArgumentException { 574 reqnn("response", response); 575 int actual = response.getStatusLine().getStatusCode(); 576 if (actual != expected) 577 throw iaex("Unexpected status code. Expected:[%s], Actual:[%s]", expected, actual); 578 return response; 579 } 580 581 /** 582 * Asserts that the specified HTTP response has the same status code as the one on the status line of this bean. 583 * 584 * @param response The HTTP response to check. Must not be <jk>null</jk>. 585 * @throws IllegalArgumentException If status code is not what was expected. 586 */ 587 protected void assertStatusCode(HttpResponse response) throws IllegalArgumentException { 588 requireStatus(response, getStatusLine().getStatusCode()); 589 } 590 591 /** 592 * Single mutation funnel — the only choke-point through which all state changes on this bean flow. 593 * 594 * <p> 595 * Every mutator (fluent setter and {@code void} interface mutator) routes through this method. The nested 596 * {@code Unmodifiable} snapshot overrides only this method to throw, which freezes the entire mutation surface with 597 * a single override. Because this is a {@link Throwable}-rooted hierarchy (no CRTP self-type), the funnel returns 598 * the base type. 599 * 600 * @param mutation The state change to apply. 601 * @return This object. 602 */ 603 protected BasicHttpException modify(Runnable mutation) { 604 mutation.run(); 605 return this; 606 } 607 608 /** 609 * Deep-freeze hook invoked from the nested {@code Unmodifiable} constructor after the snapshot copy completes. 610 * 611 * <p> 612 * The mutable sub-beans are frozen here by <b>direct field assignment</b> (never via a setter, which would route 613 * through the throwing {@link #modify(Runnable)} and blow up during construction). A leaf that adds its own mutable 614 * sub-bean overrides this method (calling {@code super.freeze()} first). 615 */ 616 protected void freeze() { 617 statusLine = statusLine.unmodifiable(); // DIRECT field write — must NOT use setStatusLine(...) 618 headers = headers.unmodifiable(); // DIRECT field write — snapshot the copied HeaderList. 619 } 620 621 /** 622 * Unmodifiable point-in-time snapshot of the enclosing {@link BasicHttpException}. 623 * 624 * <p> 625 * Its only behavioral override is {@link #modify(Runnable)}, which throws — because all mutation is funneled through 626 * {@code modify(...)}, this single override freezes the entire mutation surface. 627 */ 628 public static class Unmodifiable extends BasicHttpException implements UnmodifiableBean { 629 630 private static final long serialVersionUID = 1L; 631 632 /** 633 * Constructor. 634 * 635 * @param copyFrom The exception to snapshot. Must not be <jk>null</jk>. 636 */ 637 @SuppressWarnings({ 638 "java:S1699" // Paradigm intentionally calls the overridable freeze() from the ctor to deep-freeze sub-beans. 639 }) 640 protected Unmodifiable(BasicHttpException copyFrom) { 641 super(copyFrom); 642 freeze(); 643 } 644 645 @Override /* Overridden from BasicHttpException */ 646 protected BasicHttpException modify(Runnable mutation) { 647 throw uoex("Bean is unmodifiable."); 648 } 649 } 650}