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