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>'&lt;'</js>, <js>'&gt;'</js>, and <js>'&amp;'</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}