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