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