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.*; 019 020/** 021 * Exception thrown to trigger an error HTTP status. 022 * 023 * <p> 024 * REST methods on subclasses of <c>RestServlet</c> can throw this exception to trigger an HTTP status other than the 025 * automatically-generated <c>404</c>, <c>405</c>, and <c>500</c> statuses. 026 */ 027public class HttpException extends RuntimeException { 028 029 private static final long serialVersionUID = 1L; 030 031 private int status; 032 033 /** 034 * Constructor. 035 * 036 * @param cause The cause of this exception. 037 * @param status The HTTP status code. 038 * @param msg The status message. 039 * @param args Optional {@link MessageFormat}-style arguments. 040 */ 041 public HttpException(Throwable cause, int status, String msg, Object...args) { 042 super(message(cause, msg, args), cause); 043 this.status = status; 044 } 045 046 /** 047 * Constructor. 048 * 049 * @param msg The status message. 050 */ 051 public HttpException(String msg) { 052 super(msg, null); 053 } 054 055 private static String message(Throwable cause, String msg, Object...args) { 056 if (msg == null && cause != null) 057 return firstNonEmpty(cause.getLocalizedMessage(), cause.getClass().getName()); 058 return format(msg, args); 059 } 060 061 /** 062 * Constructor. 063 * @param cause The root exception. 064 * @param status The HTTP status code. 065 */ 066 public HttpException(Throwable cause, int status) { 067 this(cause, status, null); 068 } 069 070 /** 071 * Constructor. 072 * 073 * @param status The HTTP status code. 074 * @param msg The status message. 075 * @param args Optional {@link MessageFormat}-style arguments. 076 */ 077 public HttpException(int status, String msg, Object...args) { 078 this(null, status, msg, args); 079 } 080 081 /** 082 * Returns the root cause of this exception. 083 * 084 * <p> 085 * The root cause is the first exception in the init-cause parent chain that's not one of the following: 086 * <ul> 087 * <li>{@link HttpException} 088 * <li>{@link InvocationTargetException} 089 * </ul> 090 * 091 * @return The root cause of this exception, or <jk>null</jk> if no root cause was found. 092 */ 093 public Throwable getRootCause() { 094 Throwable t = this; 095 while(t != null) { 096 t = t.getCause(); 097 if (! (t instanceof HttpException || t instanceof InvocationTargetException)) 098 return t; 099 } 100 return null; 101 } 102 103 /** 104 * Returns all error messages from all errors in this stack. 105 * 106 * <p> 107 * Typically useful if you want to render all the error messages in the stack, but don't want to render all the 108 * stack traces too. 109 * 110 * @param scrubForXssVulnerabilities 111 * If <jk>true</jk>, replaces <js>'<'</js>, <js>'>'</js>, and <js>'&'</js> characters with spaces. 112 * @return All error messages from all errors in this stack. 113 */ 114 public String getFullStackMessage(boolean scrubForXssVulnerabilities) { 115 String msg = getMessage(); 116 StringBuilder sb = new StringBuilder(); 117 if (msg != null) { 118 if (scrubForXssVulnerabilities) 119 msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' '); 120 sb.append(msg); 121 } 122 Throwable e = getCause(); 123 while (e != null) { 124 msg = e.getMessage(); 125 if (msg != null && scrubForXssVulnerabilities) 126 msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' '); 127 String cls = e.getClass().getSimpleName(); 128 if (msg == null) 129 sb.append(format("\nCaused by ({0})", cls)); 130 else 131 sb.append(format("\nCaused by ({0}): {1}", cls, msg)); 132 e = e.getCause(); 133 } 134 return sb.toString(); 135 } 136 137 @Override /* Object */ 138 public int hashCode() { 139 int i = 0; 140 Throwable t = this; 141 while (t != null) { 142 for (StackTraceElement e : t.getStackTrace()) 143 i ^= e.hashCode(); 144 t = t.getCause(); 145 } 146 return i; 147 } 148 149 /** 150 * Set the status code on this exception. 151 * 152 * @param status The status code. 153 * @return This object (for method chaining). 154 */ 155 protected HttpException setStatus(int status) { 156 this.status = status; 157 return this; 158 } 159 160 /** 161 * Returns the HTTP status code. 162 * 163 * @return The HTTP status code. 164 */ 165 public int getStatus() { 166 return status; 167 } 168 169 // When serialized, just serialize the message itself. 170 @Override /* Object */ 171 public String toString() { 172 return emptyIfNull(getLocalizedMessage()); 173 } 174}