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.exception;
014
015import static org.apache.juneau.internal.StringUtils.*;
016
017import java.lang.reflect.*;
018import java.text.*;
019import java.util.*;
020
021import org.apache.juneau.*;
022import org.apache.juneau.annotation.*;
023import org.apache.juneau.collections.*;
024import org.apache.juneau.http.annotation.*;
025import org.apache.juneau.internal.*;
026
027/**
028 * Exception thrown to trigger an error HTTP status.
029 *
030 * <p>
031 * REST methods on subclasses of <c>RestServlet</c> can throw this exception to trigger an HTTP status other than the
032 * automatically-generated <c>404</c>, <c>405</c>, and <c>500</c> statuses.
033 */
034@Response
035public class HttpException extends BasicRuntimeException {
036
037   private static final long serialVersionUID = 1L;
038
039   private int status;
040   private AMap<String,Object> headers = AMap.of();
041
042   /**
043    * Constructor.
044    *
045    * @param cause The cause of this exception.
046    * @param status The HTTP status code.
047    * @param msg The status message.
048    * @param args Optional {@link MessageFormat}-style arguments.
049    */
050   public HttpException(Throwable cause, int status, String msg, Object...args) {
051      super(cause, message(cause, msg, args));
052      this.status = status;
053   }
054
055   /**
056    * Constructor.
057    *
058    * @param msg The status message.
059    */
060   public HttpException(String msg) {
061      this((Throwable)null, 0, msg);
062   }
063
064   /**
065    * Constructor.
066    * @param cause The root exception.
067    * @param status The HTTP status code.
068    */
069   public HttpException(Throwable cause, int status) {
070      this(cause, status, null);
071   }
072
073   /**
074    * Constructor.
075    *
076    * @param status The HTTP status code.
077    * @param msg The status message.
078    * @param args Optional {@link MessageFormat}-style arguments.
079    */
080   public HttpException(int status, String msg, Object...args) {
081      this(null, status, msg, args);
082   }
083
084   /**
085    * Returns the root cause of this exception.
086    *
087    * <p>
088    * The root cause is the first exception in the init-cause parent chain that's not one of the following:
089    * <ul>
090    *    <li>{@link HttpException}
091    *    <li>{@link InvocationTargetException}
092    * </ul>
093    *
094    * @return The root cause of this exception, or <jk>null</jk> if no root cause was found.
095    */
096   public Throwable getRootCause() {
097      Throwable t = this;
098      while(t != null) {
099         if (! (t instanceof HttpException || t instanceof InvocationTargetException))
100            return t;
101         t = t.getCause();
102      }
103      return null;
104   }
105
106   /**
107    * Returns all error messages from all errors in this stack.
108    *
109    * <p>
110    * Typically useful if you want to render all the error messages in the stack, but don't want to render all the
111    * stack traces too.
112    *
113    * @param scrubForXssVulnerabilities
114    *    If <jk>true</jk>, replaces <js>'&lt;'</js>, <js>'&gt;'</js>, and <js>'&amp;'</js> characters with spaces.
115    * @return All error messages from all errors in this stack.
116    */
117   public String getFullStackMessage(boolean scrubForXssVulnerabilities) {
118      String msg = getMessage();
119      StringBuilder sb = new StringBuilder();
120      if (msg != null) {
121         if (scrubForXssVulnerabilities)
122            msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' ');
123         sb.append(msg);
124      }
125      Throwable e = getCause();
126      while (e != null) {
127         msg = e.getMessage();
128         if (msg != null && scrubForXssVulnerabilities)
129            msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' ');
130         String cls = e.getClass().getSimpleName();
131         if (msg == null)
132            sb.append(format("\nCaused by ({0})", cls));
133         else
134            sb.append(format("\nCaused by ({0}): {1}", cls, msg));
135         e = e.getCause();
136      }
137      return sb.toString();
138   }
139
140   @Override /* Object */
141   public int hashCode() {
142      int i = 0;
143      Throwable t = this;
144      while (t != null) {
145         for (StackTraceElement e : t.getStackTrace())
146         i ^= e.hashCode();
147         t = t.getCause();
148      }
149      return i;
150   }
151
152   /**
153    * Set the status code on this exception.
154    *
155    * @param status The status code.
156    * @return This object (for method chaining).
157    */
158   public HttpException setStatus(int status) {
159      this.status = status;
160      return this;
161   }
162
163   /**
164    * Returns the HTTP status code.
165    *
166    * @return The HTTP status code.
167    */
168   @ResponseStatus
169   public int getStatus() {
170      return status;
171   }
172
173   /**
174    * Add an HTTP header to this exception.
175    *
176    * @param name The header name.
177    * @param val The header value.
178    * @return This object (for method chaining).
179    */
180   @FluentSetter
181   public HttpException header(String name, Object val) {
182      headers.a(name, val);
183      return this;
184   }
185
186   /**
187    * Returns the headers associated with this exception.
188    *
189    * @return The headers associated with this exception.
190    */
191   @ResponseHeader("*")
192   @BeanIgnore
193   public Map<String,Object> getHeaders() {
194      return headers;
195   }
196
197   private static String message(Throwable cause, String msg, Object...args) {
198      if (msg == null && cause != null)
199         return firstNonEmpty(cause.getLocalizedMessage(), cause.getClass().getName());
200      return format(msg, args);
201   }
202
203   // When serialized, just serialize the message itself.
204   @Override /* Object */
205   public String toString() {
206      return emptyIfNull(getLocalizedMessage());
207   }
208
209   // <FluentSetters>
210
211   // </FluentSetters>
212}