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.rest;
014
015import static org.apache.juneau.internal.StringUtils.*;
016
017import java.lang.reflect.*;
018import java.text.*;
019
020import org.apache.juneau.rest.annotation.*;
021
022/**
023 * Exception thrown to trigger an error HTTP status.
024 *
025 * <p>
026 * REST methods on subclasses of {@link RestServlet} can throw this exception to trigger an HTTP status other than the
027 * automatically-generated <code>404</code>, <code>405</code>, and <code>500</code> statuses.
028 */
029public class RestException extends RuntimeException {
030
031   private static final long serialVersionUID = 1L;
032
033   private int status;
034   private int occurrence;
035
036   /**
037    * Constructor.
038    *
039    * @param cause The cause of this exception.
040    * @param status The HTTP status code.
041    * @param msg The status message.
042    * @param args Optional {@link MessageFormat}-style arguments.
043    */
044   public RestException(Throwable cause, int status, String msg, Object...args) {
045      super(message(cause, msg, args), cause);
046      this.status = status;
047   }
048
049   /**
050    * Constructor.
051    *
052    * @param msg The status message.
053    */
054   public RestException(String msg) {
055      super(msg, null);
056   }
057
058   private static String message(Throwable cause, String msg, Object...args) {
059      if (msg == null && cause != null)
060         return firstNonEmpty(cause.getLocalizedMessage(), cause.getClass().getName());
061      return format(msg, args);
062   }
063
064   /**
065    * Constructor.
066    * @param cause The root exception.
067    * @param status The HTTP status code.
068    */
069   public RestException(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 RestException(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 RestException}
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         t = t.getCause();
100         if (! (t instanceof RestException || t instanceof InvocationTargetException))
101            return t;
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 occurrence count on this exception.
154    *
155    * @param occurrence The number of times this exception has occurred.
156    * @return This object (for method chaining).
157    */
158   protected RestException setOccurrence(int occurrence) {
159      this.occurrence = occurrence;
160      return this;
161   }
162
163   /**
164    * Set the status code on this exception.
165    *
166    * @param status The status code.
167    * @return This object (for method chaining).
168    */
169   protected RestException setStatus(int status) {
170      this.status = status;
171      return this;
172   }
173
174   /**
175    * Returns the number of times this exception occurred on this servlet.
176    *
177    * @return
178    *    The occurrence number if {@link RestResource#useStackTraceHashes() @RestResource(useStackTraceHashes)} is enabled, or <code>0</code> otherwise.
179    */
180   public int getOccurrence() {
181      return occurrence;
182   }
183
184   /**
185    * Returns the HTTP status code.
186    *
187    * @return The HTTP status code.
188    */
189   public int getStatus() {
190      return status;
191   }
192
193   // When serialized, just serialize the message itself.
194   @Override /* Object */
195   public String toString() {
196      return emptyIfNull(getLocalizedMessage());
197   }
198
199   @SuppressWarnings("javadoc")
200   @Deprecated
201   public RestException(int status, Throwable cause) {
202      this(cause, status);
203   }
204
205   @Override
206   @Deprecated
207   public synchronized RestException initCause(Throwable cause) {
208      super.initCause(cause);
209      return this;
210   }
211}