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