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>'<'</js>, <js>'>'</js>, and <js>'&'</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}