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.response; 018 019import static org.apache.juneau.assertions.Assertions.*; 020import static org.apache.juneau.commons.utils.AssertionUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022import static org.apache.juneau.http.HttpEntities.*; 023import static org.apache.juneau.http.HttpHeaders.*; 024 025import java.lang.reflect.*; 026import java.text.*; 027import java.util.*; 028 029import org.apache.http.*; 030import org.apache.http.impl.*; 031import org.apache.http.params.*; 032import org.apache.juneau.*; 033import org.apache.juneau.annotation.*; 034import org.apache.juneau.http.*; 035import org.apache.juneau.http.header.*; 036 037/** 038 * Basic implementation of the {@link HttpResponse} interface for error responses. 039 * 040 * <p> 041 * Although this class implements the various setters defined on the {@link HttpResponse} interface, it's in general 042 * going to be more efficient to set the status/headers/content of this bean through the builder. 043 * 044 * <p> 045 * If the <c>unmodifiable</c> flag is set on this bean, calls to the setters will throw {@link UnsupportedOperationException} exceptions. 046 * 047 * <h5 class='section'>Notes:</h5><ul> 048 * <li class='warn'>Beans are not thread safe unless they're marked as unmodifiable. 049 * </ul> 050 * 051 * <h5 class='section'>See Also:</h5><ul> 052 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommonBasics">juneau-rest-common Basics</a> 053 * </ul> 054 * 055 * @serial exclude 056 */ 057@BeanIgnore /* Use toString() to serialize */ 058public class BasicHttpException extends BasicRuntimeException implements HttpResponse { 059 060 private static final long serialVersionUID = 1L; 061 062 HeaderList headers = HeaderList.create(); 063 BasicStatusLine statusLine = new BasicStatusLine(); 064 HttpEntity content; 065 066 /** 067 * Constructor. 068 */ 069 public BasicHttpException() { 070 super((Throwable)null); 071 } 072 073 /** 074 * Constructor. 075 * 076 * <p> 077 * This is the constructor used when parsing an HTTP response. 078 * 079 * @param response The HTTP response being parsed. 080 */ 081 public BasicHttpException(HttpResponse response) { 082 super((Throwable)null); 083 var h = response.getLastHeader("Thrown"); 084 if (nn(h)) 085 setMessage(thrown(h.getValue()).asParts().get().get(0).getMessage()); 086 setHeaders(response.getAllHeaders()); 087 setContent(response.getEntity()); 088 setStatusCode(response.getStatusLine().getStatusCode()); 089 } 090 091 /** 092 * Constructor. 093 * 094 * @param statusCode The HTTP status code. 095 */ 096 public BasicHttpException(int statusCode) { 097 super((Throwable)null); 098 setStatusCode(statusCode); 099 } 100 101 /** 102 * Constructor. 103 * 104 * @param statusCode The HTTP status code. 105 * @param msg The message. Can be <jk>null</jk>. 106 * @param args Optional {@link MessageFormat}-style arguments in the message. 107 */ 108 public BasicHttpException(int statusCode, String msg, Object...args) { 109 super(msg, args); 110 setStatusCode(statusCode); 111 } 112 113 /** 114 * Constructor. 115 * 116 * @param statusCode The HTTP status code. 117 * @param causedBy The cause. Can be <jk>null</jk>. 118 */ 119 public BasicHttpException(int statusCode, Throwable causedBy) { 120 super(causedBy); 121 setStatusCode(statusCode); 122 } 123 124 /** 125 * Constructor. 126 * 127 * @param statusCode The HTTP status code. 128 * @param cause The caused-by exception. Can be <jk>null</jk>. 129 * @param msg The message. Can be <jk>null</jk>. 130 * @param args The message arguments. 131 */ 132 public BasicHttpException(int statusCode, Throwable cause, String msg, Object...args) { 133 super(cause, msg, args); 134 setStatusCode(statusCode); 135 setContent(f(msg, args)); 136 } 137 138 /** 139 * Copy constructor. 140 * 141 * @param copyFrom The bean to copy. 142 */ 143 protected BasicHttpException(BasicHttpException copyFrom) { 144 this(0, copyFrom.getCause(), copyFrom.getMessage()); 145 setStatusLine(copyFrom.statusLine.copy()); 146 } 147 148 @Override /* Overridden from HttpMessage */ 149 public void addHeader(Header value) { 150 headers.append(value); 151 } 152 153 @Override /* Overridden from HttpMessage */ 154 public void addHeader(String name, String value) { 155 headers.append(name, value); 156 } 157 158 @Override /* Overridden from HttpMessage */ 159 public boolean containsHeader(String name) { 160 return headers.contains(name); 161 } 162 163 @Override /* Overridden from HttpMessage */ 164 public Header[] getAllHeaders() { return headers.getAll(); } 165 166 @Override /* Overridden from HttpMessage */ 167 public HttpEntity getEntity() { 168 // Constructing a StringEntity is somewhat expensive, so don't create it unless it's needed. 169 if (content == null) 170 content = stringEntity(getMessage()); 171 return content; 172 } 173 174 @Override /* Overridden from HttpMessage */ 175 public Header getFirstHeader(String name) { 176 return headers.getFirst(name).orElse(null); 177 } 178 179 /** 180 * Returns all error messages from all errors in this stack. 181 * 182 * <p> 183 * Typically useful if you want to render all the error messages in the stack, but don't want to render all the 184 * stack traces too. 185 * 186 * @param scrubForXssVulnerabilities 187 * If <jk>true</jk>, replaces <js>'<'</js>, <js>'>'</js>, and <js>'&'</js> characters with spaces. 188 * @return All error messages from all errors in this stack. 189 */ 190 public String getFullStackMessage(boolean scrubForXssVulnerabilities) { 191 var msg = getMessage(); 192 StringBuilder sb = new StringBuilder(); 193 if (nn(msg)) { 194 if (scrubForXssVulnerabilities) 195 msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' '); 196 sb.append(msg); 197 } 198 Throwable e = getCause(); 199 while (nn(e)) { 200 msg = e.getMessage(); 201 if (nn(msg) && scrubForXssVulnerabilities) 202 msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' '); 203 var cls = cns(e); 204 if (msg == null) 205 sb.append(f("\nCaused by ({0})", cls)); 206 else 207 sb.append(f("\nCaused by ({0}): {1}", cls, msg)); 208 e = e.getCause(); 209 } 210 return sb.toString(); 211 } 212 213 /** 214 * Returns access to the underlying builder for the headers. 215 * 216 * @return The underlying builder for the headers. 217 */ 218 public HeaderList getHeaders() { 219 return headers; 220 } 221 222 @Override /* Overridden from HttpMessage */ 223 public Header[] getHeaders(String name) { 224 return headers.getAll(name); 225 } 226 227 @Override /* Overridden from HttpMessage */ 228 public Header getLastHeader(String name) { 229 return headers.getLast(name).orElse(null); 230 } 231 232 @Override /* Overridden from HttpMessage */ 233 public Locale getLocale() { return statusLine.getLocale(); } 234 235 @Override /* Overridden from Throwable */ 236 public String getMessage() { 237 String m = super.getMessage(); 238 if (m == null && nn(getCause())) 239 m = getCause().getMessage(); 240 if (m == null) 241 m = statusLine.getReasonPhrase(); 242 return m; 243 } 244 245 @SuppressWarnings("deprecation") 246 @Override /* Overridden from HttpMessage */ 247 public HttpParams getParams() { return null; } 248 249 @Override /* Overridden from HttpMessage */ 250 public ProtocolVersion getProtocolVersion() { return statusLine.getProtocolVersion(); } 251 252 /** 253 * Returns the root cause of this exception. 254 * 255 * <p> 256 * The root cause is the first exception in the init-cause parent chain that's not one of the following: 257 * <ul> 258 * <li>{@link BasicHttpException} 259 * <li>{@link InvocationTargetException} 260 * </ul> 261 * 262 * @return The root cause of this exception, or <jk>null</jk> if no root cause was found. 263 */ 264 public Throwable getRootCause() { 265 Throwable t = this; 266 while (nn(t)) { 267 if (! (t instanceof BasicHttpException || t instanceof InvocationTargetException)) 268 return t; 269 t = t.getCause(); 270 } 271 return null; 272 } 273 274 @Override /* Overridden from HttpMessage */ 275 public StatusLine getStatusLine() { return statusLine; } 276 277 @Override /* Overridden from Object */ 278 public int hashCode() { 279 int i = 0; 280 Throwable t = this; 281 while (nn(t)) { 282 for (var e : t.getStackTrace()) 283 i ^= e.hashCode(); 284 t = t.getCause(); 285 } 286 return i; 287 } 288 289 @Override /* Overridden from HttpMessage */ 290 public HeaderIterator headerIterator() { 291 return headers.headerIterator(); 292 } 293 294 @Override /* Overridden from HttpMessage */ 295 public HeaderIterator headerIterator(String name) { 296 return headers.headerIterator(name); 297 } 298 299 @Override /* Overridden from HttpMessage */ 300 public void removeHeader(Header value) { 301 headers.remove(value); 302 } 303 304 @Override /* Overridden from HttpMessage */ 305 public void removeHeaders(String name) { 306 headers.remove(name); 307 } 308 309 /** 310 * Sets the body on this response. 311 * 312 * @param value The body on this response. 313 * @return This object. 314 */ 315 public BasicHttpException setContent(HttpEntity value) { 316 content = value; 317 return this; 318 } 319 320 /** 321 * Sets the body on this response. 322 * 323 * @param value The body on this response. 324 * @return This object. 325 */ 326 public BasicHttpException setContent(String value) { 327 setContent(stringEntity(value)); 328 return this; 329 } 330 331 @Override /* Overridden from HttpMessage */ 332 public void setEntity(HttpEntity entity) { 333 this.content = entity; 334 } 335 336 @Override /* Overridden from HttpMessage */ 337 public void setHeader(Header value) { 338 headers.set(value); 339 } 340 341 @Override /* Overridden from HttpMessage */ 342 public void setHeader(String name, String value) { 343 headers.set(name, value); 344 } 345 346 /** 347 * Sets a header on this response. 348 * 349 * @param name The header name. 350 * @param value The header value. 351 * @return This object. 352 */ 353 public BasicHttpException setHeader2(String name, Object value) { 354 headers.set(name, value); 355 return this; 356 } 357 358 @Override /* Overridden from HttpMessage */ 359 public void setHeaders(Header[] values) { 360 headers.removeAll().append(values); 361 } 362 363 /** 364 * Sets the specified headers on this response. 365 * 366 * @param value The new value. 367 * @return This object. 368 */ 369 public BasicHttpException setHeaders(HeaderList value) { 370 headers = value.copy(); 371 return this; 372 } 373 374 /** 375 * Sets the specified headers on this response. 376 * 377 * @param values The headers to set. <jk>null</jk> values are ignored. 378 * @return This object. 379 */ 380 public BasicHttpException setHeaders(List<Header> values) { 381 headers.set(values); 382 return this; 383 } 384 385 /** 386 * Sets multiple headers on this response. 387 * 388 * @param values The headers to add. 389 * @return This object. 390 */ 391 public BasicHttpException setHeaders2(Header...values) { 392 headers.set(values); 393 return this; 394 } 395 396 @Override /* Overridden from HttpMessage */ 397 public void setLocale(Locale loc) { 398 statusLine.setLocale(loc); 399 } 400 401 /** 402 * Sets the locale used to retrieve reason phrases. 403 * 404 * <p> 405 * If not specified, uses {@link Locale#getDefault()}. 406 * 407 * @param value The new value. 408 * @return This object. 409 */ 410 public BasicHttpException setLocale2(Locale value) { 411 statusLine.setLocale(value); 412 return this; 413 } 414 415 @Override /* Overridden from BasicRuntimeException */ 416 public BasicHttpException setMessage(String message, Object...args) { 417 super.setMessage(message, args); 418 return this; 419 } 420 421 @SuppressWarnings("deprecation") 422 @Override /* Overridden from HttpMessage */ 423 public void setParams(HttpParams params) {} 424 425 /** 426 * Sets the protocol version on the status line. 427 * 428 * <p> 429 * If not specified, <js>"HTTP/1.1"</js> will be used. 430 * 431 * @param value The new value. 432 * @return This object. 433 */ 434 public BasicHttpException setProtocolVersion(ProtocolVersion value) { 435 statusLine.setProtocolVersion(value); 436 return this; 437 } 438 439 @Override /* Overridden from HttpMessage */ 440 public void setReasonPhrase(String reason) throws IllegalStateException { 441 statusLine.setReasonPhrase(reason); 442 } 443 444 /** 445 * Sets the reason phrase on the status line. 446 * 447 * <p> 448 * If not specified, the reason phrase will be retrieved from the reason phrase catalog 449 * using the locale on this builder. 450 * 451 * @param value The new value. 452 * @return This object. 453 */ 454 public BasicHttpException setReasonPhrase2(String value) { 455 statusLine.setReasonPhrase(value); 456 return this; 457 } 458 459 /** 460 * Sets the reason phrase catalog used to retrieve reason phrases. 461 * 462 * <p> 463 * If not specified, uses {@link EnglishReasonPhraseCatalog}. 464 * 465 * @param value The new value. 466 * @return This object. 467 */ 468 public BasicHttpException setReasonPhraseCatalog(ReasonPhraseCatalog value) { 469 statusLine.setReasonPhraseCatalog(value); 470 return this; 471 } 472 473 @Override /* Overridden from HttpMessage */ 474 public void setStatusCode(int code) throws IllegalStateException { 475 statusLine.setStatusCode(code); 476 } 477 478 /** 479 * Same as {@link #setStatusCode(int)} but returns this object. 480 * 481 * @param code The new status code. 482 * @return This object. 483 * @throws IllegalStateException If status code could not be set. 484 */ 485 public BasicHttpException setStatusCode2(int code) throws IllegalStateException { 486 setStatusCode(code); 487 return this; 488 } 489 490 /** 491 * Sets the protocol version on the status line. 492 * 493 * <p> 494 * If not specified, <js>"HTTP/1.1"</js> will be used. 495 * 496 * @param value The new value. 497 * @return This object. 498 */ 499 public BasicHttpException setStatusLine(BasicStatusLine value) { 500 statusLine = value.copy(); 501 return this; 502 } 503 504 @Override /* Overridden from HttpMessage */ 505 public void setStatusLine(ProtocolVersion ver, int code) { 506 statusLine.setProtocolVersion(ver).setStatusCode(code); 507 } 508 509 @Override /* Overridden from HttpMessage */ 510 public void setStatusLine(ProtocolVersion ver, int code, String reason) { 511 statusLine.setProtocolVersion(ver).setReasonPhrase(reason).setStatusCode(code); 512 } 513 514 @Override /* Overridden from HttpMessage */ 515 public void setStatusLine(StatusLine value) { 516 setStatusLine(value.getProtocolVersion(), value.getStatusCode(), value.getReasonPhrase()); 517 } 518 519 /** 520 * Specifies whether this bean should be unmodifiable. 521 * <p> 522 * When enabled, attempting to set any properties on this bean will cause an {@link UnsupportedOperationException}. 523 * 524 * @return This object. 525 */ 526 public BasicHttpException setUnmodifiable() { 527 statusLine.setUnmodifiable(); 528 return this; 529 } 530 531 @Override /* Overridden from Object */ 532 public String toString() { 533 return emptyIfNull(getLocalizedMessage()); 534 } 535 536 /** 537 * Asserts that the specified HTTP response has the same status code as the one on the status line of this bean. 538 * 539 * @param response The HTTP response to check. Must not be <jk>null</jk>. 540 * @throws AssertionError If status code is not what was expected. 541 */ 542 protected void assertStatusCode(HttpResponse response) throws AssertionError { 543 assertArgNotNull("response", response); 544 int expected = getStatusLine().getStatusCode(); 545 int actual = response.getStatusLine().getStatusCode(); 546 assertInteger(actual).setMsg("Unexpected status code. Expected:[{0}], Actual:[{1}]", expected, actual).is(expected); 547 } 548}