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