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