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.classic.response;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021import static org.apache.juneau.http.classic.HttpEntities.*;
022import static org.apache.juneau.http.classic.HttpHeaders.*;
023
024import java.lang.reflect.*;
025import java.util.*;
026
027import org.apache.http.*;
028import org.apache.http.impl.*;
029import org.apache.http.params.*;
030import org.apache.juneau.http.UnmodifiableBean;
031import org.apache.juneau.http.classic.*;
032import org.apache.juneau.http.classic.header.*;
033import org.apache.juneau.marshall.*;
034
035/**
036 * Basic implementation of the {@link HttpResponse} interface for error responses.
037 *
038 * <p>
039 * Although this class implements the various setters defined on the {@link HttpResponse} interface, it's in general
040 * going to be more efficient to set the status/headers/content of this bean through the builder.
041 *
042 * <p>
043 * Immutability is expressed with the "funnel + nested {@code Unmodifiable} snapshot" paradigm: every mutator (fluent
044 * setter and {@code void} interface mutator) routes through the single protected {@link #modify(Runnable)} choke-point,
045 * and each concrete leaf gets a nested {@code X.Unmodifiable} whose only behavioral override is a throwing
046 * {@code modify(...)}.  A leaf's {@link #unmodifiable()} factory returns a point-in-time snapshot of type
047 * {@code X.Unmodifiable}; any attempt to set a property on it throws an {@link UnsupportedOperationException}.
048 *
049 * <p>
050 * This is a {@link Throwable}-rooted hierarchy, so (unlike {@link BasicHttpResponse}) it cannot be a self-typed (CRTP)
051 * root — JLS &sect;8.1.2 forbids a generic {@code Throwable}.  The funnel therefore returns the base type and each leaf
052 * keeps its covariant setter overrides.
053 *
054 * <h5 class='section'>Notes:</h5><ul>
055 *    <li class='warn'>Beans are not thread safe unless they're marked as unmodifiable.
056 * </ul>
057 *
058 * <h5 class='section'>See Also:</h5><ul>
059 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a>
060 * </ul>
061 *
062 * @serial exclude
063 */
064@Marshalled(as=MarshalledAs.STRING)
065@SuppressWarnings({
066   "deprecation", // Uses deprecated HttpMessage API
067   "java:S110", // Deep exception-hierarchy inheritance is structural and intentional; flattening would break the HTTP-response exception model.
068   "java:S1104", // Package-private headers/statusLine/content fields are reassigned by setters and subclass constructors, so they cannot be final or private
069   "java:S1165", // headers/statusLine/content are replaced after construction by setters, so the exception's fields cannot be final
070   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
071})
072public class BasicHttpException extends BasicRuntimeException implements HttpResponse {
073
074   private static final long serialVersionUID = 1L;
075
076   HeaderList headers = HeaderList.create();
077   transient BasicStatusLine statusLine = new BasicStatusLine();
078   transient HttpEntity content;
079
080   /**
081    * Constructor.
082    */
083   public BasicHttpException() {
084      super((Throwable)null);
085   }
086
087   /**
088    * Constructor.
089    *
090    * <p>
091    * This is the constructor used when parsing an HTTP response.
092    *
093    * @param response The HTTP response being parsed.  Must not be <jk>null</jk>.
094    */
095   public BasicHttpException(HttpResponse response) {
096      super((Throwable)null);
097      reqnn("response", response);
098      var h = response.getLastHeader("Thrown");
099      if (nn(h)) {
100         var partsOpt = thrown(h.getValue()).asParts();
101         if (partsOpt.isPresent() && !partsOpt.get().isEmpty())
102            setMessage(partsOpt.get().get(0).getMessage());
103      }
104      setHeaders(response.getAllHeaders());
105      setContent(response.getEntity());
106      setStatusCode(response.getStatusLine().getStatusCode());
107   }
108
109   /**
110    * Constructor.
111    *
112    * @param statusCode The HTTP status code.
113    */
114   public BasicHttpException(int statusCode) {
115      super((Throwable)null);
116      setStatusCode(statusCode);
117   }
118
119   /**
120    * Constructor.
121    *
122    * @param statusCode The HTTP status code.
123    * @param msg The message.  Can be <jk>null</jk>, in which case the reason phrase is used as the message.
124    * @param args Optional <c>printf</c>-style (<js>"%s"</js>) arguments in the message.
125    */
126   public BasicHttpException(int statusCode, String msg, Object...args) {
127      super(msg, args);
128      setStatusCode(statusCode);
129   }
130
131   /**
132    * Constructor.
133    *
134    * @param statusCode The HTTP status code.
135    * @param causedBy The cause.  Can be <jk>null</jk>, in which case no cause is chained and the reason phrase is used as the message.
136    */
137   public BasicHttpException(int statusCode, Throwable causedBy) {
138      super(causedBy);
139      setStatusCode(statusCode);
140   }
141
142   /**
143    * Constructor.
144    *
145    * @param statusCode The HTTP status code.
146    * @param cause The caused-by exception.  Can be <jk>null</jk> (no cause is chained).
147    * @param msg The message.  Can be <jk>null</jk>, in which case the cause's message is used, or the reason phrase if the cause is also <jk>null</jk> or has no message.
148    * @param args The message arguments.
149    */
150   public BasicHttpException(int statusCode, Throwable cause, String msg, Object...args) {
151      super(cause, msg, args);
152      setStatusCode(statusCode);
153      setContent(f(msg, args));
154   }
155
156   /**
157    * Copy constructor.
158    *
159    * <p>
160    * The mutable sub-beans are deep-copied by <b>direct field assignment</b>, never through a funneled setter.  This is
161    * the snapshot path used by {@link #unmodifiable()}: the constructor runs inside an {@code Unmodifiable} instance
162    * whose {@link #modify(Runnable)} already throws, so a setter-based copy would blow up during construction.
163    *
164    * @param copyFrom The bean to copy.  Must not be <jk>null</jk>.
165    */
166   protected BasicHttpException(BasicHttpException copyFrom) {
167      super(copyFrom.getCause(), "%s", copyFrom.getMessage());
168      statusLine = copyFrom.statusLine.copy();
169      headers = copyFrom.headers.copy();
170      content = copyFrom.content;
171   }
172
173   @Override /* Overridden from HttpMessage */
174   public void addHeader(Header value) {
175      modify(() -> headers.append(value));
176   }
177
178   @Override /* Overridden from HttpMessage */
179   public void addHeader(String name, String value) {
180      modify(() -> headers.append(name, value));
181   }
182
183   @Override /* Overridden from HttpMessage */
184   public boolean containsHeader(String name) {
185      return headers.contains(name);
186   }
187
188   @Override /* Overridden from HttpMessage */
189   public Header[] getAllHeaders() { return headers.getAll(); }
190
191   @Override /* Overridden from HttpMessage */
192   public HttpEntity getEntity() {
193      // Constructing a StringEntity is somewhat expensive, so don't create it unless it's needed.
194      if (content == null)
195         content = stringEntity(getMessage());
196      return content;
197   }
198
199   @Override /* Overridden from HttpMessage */
200   public Header getFirstHeader(String name) {
201      return headers.getFirst(name).orElse(null);
202   }
203
204   /**
205    * Returns all error messages from all errors in this stack.
206    *
207    * <p>
208    * Typically useful if you want to render all the error messages in the stack, but don't want to render all the
209    * stack traces too.
210    *
211    * @param scrubForXssVulnerabilities
212    *    If <jk>true</jk>, replaces <js>'&lt;'</js>, <js>'&gt;'</js>, and <js>'&amp;'</js> characters with spaces.
213    * @return All error messages from all errors in this stack.
214    */
215   public String getFullStackMessage(boolean scrubForXssVulnerabilities) {
216      var msg = getMessage();
217      StringBuilder sb = new StringBuilder();
218      if (nn(msg)) {
219         if (scrubForXssVulnerabilities)
220            msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' ');
221         sb.append(msg);
222      }
223      Throwable e = getCause();
224      while (nn(e)) {
225         msg = e.getMessage();
226         if (nn(msg) && scrubForXssVulnerabilities)
227            msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' ');
228         var cls = cns(e);
229         if (msg == null)
230            sb.append(f("\nCaused by (%s)", cls));
231         else
232            sb.append(f("\nCaused by (%s): %s", cls, msg));
233         e = e.getCause();
234      }
235      return sb.toString();
236   }
237
238   /**
239    * Returns access to the underlying builder for the headers.
240    *
241    * @return The underlying builder for the headers.
242    */
243   public HeaderList getHeaders() {
244      return headers;
245   }
246
247   @Override /* Overridden from HttpMessage */
248   public Header[] getHeaders(String name) {
249      return headers.getAll(name);
250   }
251
252   @Override /* Overridden from HttpMessage */
253   public Header getLastHeader(String name) {
254      return headers.getLast(name).orElse(null);
255   }
256
257   @Override /* Overridden from HttpMessage */
258   public Locale getLocale() { return statusLine.getLocale(); }
259
260   @Override /* Overridden from Throwable */
261   public String getMessage() {
262      String m = super.getMessage();
263      if (m == null && nn(getCause()))
264         m = getCause().getMessage();
265      if (m == null)
266         m = statusLine.getReasonPhrase();
267      return m;
268   }
269
270   @Override /* Overridden from HttpMessage */
271   public HttpParams getParams() { return null; }
272
273   @Override /* Overridden from HttpMessage */
274   public ProtocolVersion getProtocolVersion() { return statusLine.getProtocolVersion(); }
275
276   /**
277    * Returns the root cause of this exception.
278    *
279    * <p>
280    * The root cause is the first exception in the init-cause parent chain that's not one of the following:
281    * <ul>
282    *    <li>{@link BasicHttpException}
283    *    <li>{@link InvocationTargetException}
284    * </ul>
285    *
286    * @return The root cause of this exception, or <jk>null</jk> if no root cause was found.
287    */
288   public Throwable getRootCause() {
289      Throwable t = this;
290      while (nn(t)) {
291         if (! (t instanceof BasicHttpException || t instanceof InvocationTargetException))
292            return t;
293         t = t.getCause();
294      }
295      return null;
296   }
297
298   @Override /* Overridden from HttpMessage */
299   public StatusLine getStatusLine() { return statusLine; }
300
301   @Override /* Overridden from Object */
302   public boolean equals(Object o) {
303      // Content equality.  The 'content' HttpEntity is deliberately excluded: it is a wrapper without value
304      // semantics (two StringEntity instances of the same text are not equal), and it is derived from the message
305      // (getEntity() lazily builds stringEntity(getMessage())), which is already compared here.
306      return o instanceof BasicHttpException other && eq(this, other, (x, y) ->
307         eq(x.statusLine, y.statusLine) && eq(x.headers, y.headers) && eq(x.getMessage(), y.getMessage()));
308   }
309
310   @Override /* Overridden from Object */
311   public int hashCode() {
312      return h(statusLine, headers, getMessage());
313   }
314
315   @Override /* Overridden from HttpMessage */
316   public HeaderIterator headerIterator() {
317      return headers.headerIterator();
318   }
319
320   @Override /* Overridden from HttpMessage */
321   public HeaderIterator headerIterator(String name) {
322      return headers.headerIterator(name);
323   }
324
325   /**
326    * Returns <jk>true</jk> if this bean is unmodifiable.
327    *
328    * @return <jk>true</jk> if this bean is an {@link UnmodifiableBean} snapshot.
329    */
330   public boolean isUnmodifiable() { return this instanceof UnmodifiableBean; }
331
332   @Override /* Overridden from HttpMessage */
333   public void removeHeader(Header value) {
334      modify(() -> headers.remove(value));
335   }
336
337   @Override /* Overridden from HttpMessage */
338   public void removeHeaders(String name) {
339      modify(() -> headers.remove(name));
340   }
341
342   /**
343    * Sets the body on this response.
344    *
345    * @param value The body on this response.  Can be <jk>null</jk> to unset the property.
346    * @return This object.
347    */
348   public BasicHttpException setContent(HttpEntity value) {
349      return modify(() -> content = value);
350   }
351
352   /**
353    * Sets the body on this response.
354    *
355    * @param value The body on this response.  Can be <jk>null</jk> (treated as an empty string).
356    * @return This object.
357    */
358   public BasicHttpException setContent(String value) {
359      return modify(() -> content = stringEntity(value));
360   }
361
362   @Override /* Overridden from HttpMessage */
363   public void setEntity(HttpEntity entity) {
364      modify(() -> content = entity);
365   }
366
367   @Override /* Overridden from HttpMessage */
368   public void setHeader(Header value) {
369      modify(() -> headers.set(value));
370   }
371
372   @Override /* Overridden from HttpMessage */
373   public void setHeader(String name, String value) {
374      modify(() -> headers.set(name, value));
375   }
376
377   /**
378    * Sets a header on this response.
379    *
380    * @param name The header name.
381    * @param value The header value.
382    * @return This object.
383    */
384   public BasicHttpException setHeader2(String name, Object value) {
385      return modify(() -> headers.set(name, value));
386   }
387
388   @Override /* Overridden from HttpMessage */
389   public void setHeaders(Header[] values) {
390      modify(() -> headers.removeAll().append(values));
391   }
392
393   /**
394    * Sets the specified headers on this response.
395    *
396    * @param value The new value.  Must not be <jk>null</jk>.
397    * @return This object.
398    */
399   public BasicHttpException setHeaders(HeaderList value) {
400      return modify(() -> headers = value.copy());
401   }
402
403   /**
404    * Sets the specified headers on this response.
405    *
406    * @param values The headers to set.  <jk>null</jk> values are ignored.
407    * @return This object.
408    */
409   public BasicHttpException setHeaders(List<Header> values) {
410      return modify(() -> headers.set(values));
411   }
412
413   /**
414    * Sets multiple headers on this response.
415    *
416    * @param values The headers to add.
417    * @return This object.
418    */
419   public BasicHttpException setHeaders2(Header...values) {
420      return modify(() -> headers.set(values));
421   }
422
423   @Override /* Overridden from HttpMessage */
424   public void setLocale(Locale loc) {
425      modify(() -> statusLine.setLocale(loc));
426   }
427
428   /**
429    * Sets the locale used to retrieve reason phrases.
430    *
431    * <p>
432    * If not specified, uses {@link Locale#getDefault()}.
433    *
434    * @param value The new value.  Can be <jk>null</jk>.  The effect is implementation-dependent, depending on the configured {@link ReasonPhraseCatalog} (e.g. ignored by {@link EnglishReasonPhraseCatalog}).
435    * @return This object.
436    */
437   public BasicHttpException setLocale2(Locale value) {
438      return modify(() -> statusLine.setLocale(value));
439   }
440
441   @Override /* Overridden from BasicRuntimeException */
442   public BasicHttpException setMessage(String message, Object...args) {
443      return modify(() -> super.setMessage(message, args));
444   }
445
446   @Override /* Overridden from HttpMessage */
447   public void setParams(HttpParams params) {
448      // Deprecated optional interface method; routed through the funnel so it is frozen on unmodifiable snapshots.
449      modify(() -> { /* No-op */ });
450   }
451
452   /**
453    * Sets the protocol version on the status line.
454    *
455    * <p>
456    * If not specified, <js>"HTTP/1.1"</js> will be used.
457    *
458    * @param value The new value.  Can be <jk>null</jk>, in which case the default protocol version is cleared and {@link BasicStatusLine#getProtocolVersion()} returns <jk>null</jk>.
459    * @return This object.
460    */
461   public BasicHttpException setProtocolVersion(ProtocolVersion value) {
462      return modify(() -> statusLine.setProtocolVersion(value));
463   }
464
465   @Override /* Overridden from HttpMessage */
466   public void setReasonPhrase(String reason) throws IllegalStateException {
467      modify(() -> statusLine.setReasonPhrase(reason));
468   }
469
470   /**
471    * Sets the reason phrase on the status line.
472    *
473    * <p>
474    * If not specified, the reason phrase will be retrieved from the reason phrase catalog
475    * using the locale on this builder.
476    *
477    * @param value The new value.  Can be <jk>null</jk> to fall back to the reason phrase catalog.
478    * @return This object.
479    */
480   public BasicHttpException setReasonPhrase2(String value) {
481      return modify(() -> statusLine.setReasonPhrase(value));
482   }
483
484   /**
485    * Sets the reason phrase catalog used to retrieve reason phrases.
486    *
487    * <p>
488    * If not specified, uses {@link EnglishReasonPhraseCatalog}.
489    *
490    * @param value The new value.  Can be <jk>null</jk> to fall back to {@link EnglishReasonPhraseCatalog#INSTANCE}.
491    * @return This object.
492    */
493   public BasicHttpException setReasonPhraseCatalog(ReasonPhraseCatalog value) {
494      return modify(() -> statusLine.setReasonPhraseCatalog(value));
495   }
496
497   @Override /* Overridden from HttpMessage */
498   public void setStatusCode(int code) throws IllegalStateException {
499      modify(() -> statusLine.setStatusCode(code));
500   }
501
502   /**
503    * Same as {@link #setStatusCode(int)} but returns this object.
504    *
505    * @param code The new status code.
506    * @return This object.
507    * @throws IllegalStateException If status code could not be set.
508    */
509   public BasicHttpException setStatusCode2(int code) throws IllegalStateException {
510      return modify(() -> statusLine.setStatusCode(code));
511   }
512
513   /**
514    * Sets the protocol version on the status line.
515    *
516    * <p>
517    * If not specified, <js>"HTTP/1.1"</js> will be used.
518    *
519    * @param value The new value.  Must not be <jk>null</jk>.
520    * @return This object.
521    */
522   public BasicHttpException setStatusLine(BasicStatusLine value) {
523      return modify(() -> statusLine = value.copy());
524   }
525
526   @Override /* Overridden from HttpMessage */
527   public void setStatusLine(ProtocolVersion ver, int code) {
528      modify(() -> statusLine.setProtocolVersion(ver).setStatusCode(code));
529   }
530
531   @Override /* Overridden from HttpMessage */
532   public void setStatusLine(ProtocolVersion ver, int code, String reason) {
533      modify(() -> statusLine.setProtocolVersion(ver).setReasonPhrase(reason).setStatusCode(code));
534   }
535
536   @Override /* Overridden from HttpMessage */
537   public void setStatusLine(StatusLine value) {
538      modify(() -> statusLine.setProtocolVersion(value.getProtocolVersion()).setReasonPhrase(value.getReasonPhrase()).setStatusCode(value.getStatusCode()));
539   }
540
541   /**
542    * Returns an unmodifiable snapshot of this bean.
543    *
544    * <p>
545    * The returned instance is a point-in-time copy of the leaf's nested {@code Unmodifiable} type; any attempt to set a
546    * property on it throws an {@link UnsupportedOperationException}.  This method is idempotent: if this bean is already
547    * unmodifiable it returns itself rather than taking another snapshot.  The receiver is left unchanged (and still
548    * modifiable unless it was already unmodifiable).
549    *
550    * @return An unmodifiable snapshot of this bean, or this bean if it is already unmodifiable.
551    */
552   public BasicHttpException unmodifiable() {
553      return this instanceof UnmodifiableBean ? this : new Unmodifiable(this);
554   }
555
556   @Override /* Overridden from Object */
557   public String toString() {
558      return emptyIfNull(getLocalizedMessage());
559   }
560
561   /**
562    * Verifies that the specified HTTP response has the expected status code and returns it.
563    *
564    * <p>
565    * Used by the <c>Xxx(HttpResponse)</c> constructors of the concrete response classes to validate the incoming
566    * response <i>before</i> its status line is copied.
567    *
568    * @param response The HTTP response to check.  Must not be <jk>null</jk>.
569    * @param expected The status code the response is required to have.
570    * @return The same response.
571    * @throws IllegalArgumentException If the response status code is not what was expected.
572    */
573   static HttpResponse requireStatus(HttpResponse response, int expected) throws IllegalArgumentException {
574      reqnn("response", response);
575      int actual = response.getStatusLine().getStatusCode();
576      if (actual != expected)
577         throw iaex("Unexpected status code.  Expected:[%s], Actual:[%s]", expected, actual);
578      return response;
579   }
580
581   /**
582    * Asserts that the specified HTTP response has the same status code as the one on the status line of this bean.
583    *
584    * @param response The HTTP response to check.  Must not be <jk>null</jk>.
585    * @throws IllegalArgumentException If status code is not what was expected.
586    */
587   protected void assertStatusCode(HttpResponse response) throws IllegalArgumentException {
588      requireStatus(response, getStatusLine().getStatusCode());
589   }
590
591   /**
592    * Single mutation funnel — the only choke-point through which all state changes on this bean flow.
593    *
594    * <p>
595    * Every mutator (fluent setter and {@code void} interface mutator) routes through this method.  The nested
596    * {@code Unmodifiable} snapshot overrides only this method to throw, which freezes the entire mutation surface with
597    * a single override.  Because this is a {@link Throwable}-rooted hierarchy (no CRTP self-type), the funnel returns
598    * the base type.
599    *
600    * @param mutation The state change to apply.
601    * @return This object.
602    */
603   protected BasicHttpException modify(Runnable mutation) {
604      mutation.run();
605      return this;
606   }
607
608   /**
609    * Deep-freeze hook invoked from the nested {@code Unmodifiable} constructor after the snapshot copy completes.
610    *
611    * <p>
612    * The mutable sub-beans are frozen here by <b>direct field assignment</b> (never via a setter, which would route
613    * through the throwing {@link #modify(Runnable)} and blow up during construction).  A leaf that adds its own mutable
614    * sub-bean overrides this method (calling {@code super.freeze()} first).
615    */
616   protected void freeze() {
617      statusLine = statusLine.unmodifiable();   // DIRECT field write — must NOT use setStatusLine(...)
618      headers = headers.unmodifiable();         // DIRECT field write — snapshot the copied HeaderList.
619   }
620
621   /**
622    * Unmodifiable point-in-time snapshot of the enclosing {@link BasicHttpException}.
623    *
624    * <p>
625    * Its only behavioral override is {@link #modify(Runnable)}, which throws — because all mutation is funneled through
626    * {@code modify(...)}, this single override freezes the entire mutation surface.
627    */
628   public static class Unmodifiable extends BasicHttpException implements UnmodifiableBean {
629
630      private static final long serialVersionUID = 1L;
631
632      /**
633       * Constructor.
634       *
635       * @param copyFrom The exception to snapshot.  Must not be <jk>null</jk>.
636       */
637      @SuppressWarnings({
638         "java:S1699" // Paradigm intentionally calls the overridable freeze() from the ctor to deep-freeze sub-beans.
639      })
640      protected Unmodifiable(BasicHttpException copyFrom) {
641         super(copyFrom);
642         freeze();
643      }
644
645      @Override /* Overridden from BasicHttpException */
646      protected BasicHttpException modify(Runnable mutation) {
647         throw uoex("Bean is unmodifiable.");
648      }
649   }
650}