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 <c>404</c>, <c>405</c>, and <c>500</c> statuses.
028 */
029public class RestException extends RuntimeException {
030
031   private static final long serialVersionUID = 1L;
032
033   private int status;
034   @Deprecated 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    * @deprecated Not used by new logging API.
158    */
159   @Deprecated
160   protected RestException setOccurrence(int occurrence) {
161      this.occurrence = occurrence;
162      return this;
163   }
164
165   /**
166    * Set the status code on this exception.
167    *
168    * @param status The status code.
169    * @return This object (for method chaining).
170    */
171   protected RestException setStatus(int status) {
172      this.status = status;
173      return this;
174   }
175
176   /**
177    * Returns the number of times this exception occurred on this servlet.
178    *
179    * @return
180    *    The occurrence number if {@link RestResource#useStackTraceHashes() @RestResource(useStackTraceHashes)} is enabled, or <c>0</c> otherwise.
181    * @deprecated Not used by new logging API.
182    */
183   @Deprecated
184   public int getOccurrence() {
185      return occurrence;
186   }
187
188   /**
189    * Returns the HTTP status code.
190    *
191    * @return The HTTP status code.
192    */
193   public int getStatus() {
194      return status;
195   }
196
197   // When serialized, just serialize the message itself.
198   @Override /* Object */
199   public String toString() {
200      return emptyIfNull(getLocalizedMessage());
201   }
202}