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