001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 */ 020 021package org.nuxeo.ecm.webengine; 022 023import java.io.PrintWriter; 024import java.io.StringWriter; 025 026import javax.servlet.http.HttpServletResponse; 027import javax.ws.rs.WebApplicationException; 028import javax.ws.rs.core.Response; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.nuxeo.common.utils.ExceptionUtils; 033import org.nuxeo.ecm.core.api.DocumentNotFoundException; 034import org.nuxeo.ecm.core.api.DocumentSecurityException; 035import org.nuxeo.ecm.webengine.model.ModuleResource; 036import org.nuxeo.ecm.webengine.model.WebContext; 037import org.nuxeo.ecm.webengine.model.exceptions.WebResourceNotFoundException; 038import org.nuxeo.ecm.webengine.model.exceptions.WebSecurityException; 039 040public class WebException extends WebApplicationException { 041 042 protected static final Log log = LogFactory.getLog(WebException.class); 043 044 private static final long serialVersionUID = 176876876786L; 045 046 protected String type; 047 048 protected Throwable cause; 049 050 protected String message; 051 052 protected int status; 053 054 public WebException() { 055 } 056 057 public WebException(Response response) { 058 super(response); 059 } 060 061 public WebException(int status) { 062 super(status); 063 this.status = status; 064 } 065 066 public WebException(Response.Status status) { 067 super(status); 068 this.status = status.getStatusCode(); 069 } 070 071 /** 072 * Use WebException.wrap() and not the constructor. 073 */ 074 protected WebException(Throwable cause, Response.Status status) { 075 super(cause, status); 076 this.cause = cause; 077 this.status = status.getStatusCode(); 078 } 079 080 protected WebException(Throwable cause, int status) { 081 super(cause, status); 082 this.cause = cause; 083 this.status = status; 084 } 085 086 protected WebException(Throwable cause) { 087 super(cause); 088 this.cause = cause; 089 } 090 091 public WebException(String message) { 092 this.message = message; 093 } 094 095 public WebException(String message, int code) { 096 super(code); 097 this.message = message; 098 this.status = code; 099 } 100 101 public WebException(String message, Throwable cause, int status) { 102 if (cause == null) { 103 throw new IllegalArgumentException("the cause parameter cannot be null"); 104 } 105 this.status = status == -1 ? getStatus(cause) : status; 106 this.cause = cause; 107 this.message = message == null ? cause.getMessage() : message; 108 this.type = cause.getClass().getName(); 109 } 110 111 public WebException(String message, Throwable cause) { 112 if (cause == null) { 113 throw new IllegalArgumentException("the cause parameter cannot be null"); 114 } 115 this.status = getStatus(cause); 116 this.cause = cause; 117 this.message = message == null ? cause.getMessage() : message; 118 this.type = cause.getClass().getName(); 119 } 120 121 public static WebException newException(String message, Throwable cause) { 122 return newException(message, cause, -1); 123 } 124 125 public static WebException newException(Throwable cause) { 126 return newException(null, cause); 127 } 128 129 public static WebException newException(String message, Throwable cause, int status) { 130 if (cause == null) { 131 throw new IllegalArgumentException("the cause parameter cannot be null"); 132 } 133 return new WebException(message, cause, status); 134 } 135 136 public static String toString(Throwable t) { 137 StringWriter sw = new StringWriter(); 138 PrintWriter pw = new PrintWriter(sw); 139 t.printStackTrace(pw); 140 pw.close(); 141 return sw.toString(); 142 } 143 144 public static WebException wrap(Throwable e) { 145 return wrap(null, e); 146 } 147 148 public static WebException wrap(String message, Throwable exception) { 149 if (exception instanceof Exception) { 150 ExceptionUtils.checkInterrupt((Exception) exception); 151 } 152 if (exception instanceof DocumentSecurityException) { 153 return new WebSecurityException(message, exception); 154 } else if (exception instanceof WebException) { 155 return (WebException) exception; 156 } else if (exception instanceof DocumentNotFoundException) { 157 return new WebResourceNotFoundException(message, exception); 158 } else { 159 return new WebException(message, exception); 160 } 161 } 162 163 public static Object handleError(WebApplicationException e) { 164 StringWriter sw = new StringWriter(); 165 PrintWriter pw = new PrintWriter(sw); 166 e.printStackTrace(pw); 167 pw.close(); 168 return Response.status(500).entity(sw.toString()).build(); 169 } 170 171 /** 172 * Tries to find the best matching HTTP status for the given exception. 173 */ 174 public static int getStatus(Throwable cause) { 175 // use a max depth of 8 to avoid infinite loops for broken exceptions 176 // which are referencing themselves as the cause 177 return getStatus(cause, 8); 178 } 179 180 public static int getStatus(Throwable cause, int depth) { 181 if (depth == 0) { 182 log.warn("Possible infinite loop! Check the exception wrapping."); 183 return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 184 } 185 if ((cause instanceof DocumentSecurityException) || (cause instanceof SecurityException) 186 || "javax.ejb.EJBAccessException".equals(cause.getClass().getName())) { 187 return HttpServletResponse.SC_FORBIDDEN; 188 } else if (cause instanceof DocumentNotFoundException || cause instanceof WebResourceNotFoundException) { 189 return HttpServletResponse.SC_NOT_FOUND; 190 } else if (cause instanceof WebSecurityException) { 191 return HttpServletResponse.SC_UNAUTHORIZED; 192 } 193 Throwable parent = cause.getCause(); 194 if (parent == cause) { 195 log.warn("Infinite loop detected! Check the exception wrapping."); 196 return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 197 } 198 if (parent != null) { 199 return getStatus(parent, depth - 1); 200 } 201 if (cause.getMessage() != null 202 && cause.getMessage().contains(DocumentNotFoundException.class.getName())) { 203 log.warn("Badly wrapped exception: found a DocumentNotFoundException" 204 + " message but no DocumentNotFoundException", cause); 205 return HttpServletResponse.SC_NOT_FOUND; 206 } 207 return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 208 } 209 210 public static boolean isSecurityError(Throwable t) { 211 return getStatus(t) == HttpServletResponse.SC_FORBIDDEN; 212 } 213 214 /** 215 * For compatibility only. 216 */ 217 @Deprecated 218 public static Response toResponse(Throwable t) { 219 return new WebException(t).toResponse(); 220 } 221 222 @Override 223 public String getMessage() { 224 return message; 225 } 226 227 public int getStatusCode() { 228 return status; 229 } 230 231 public String getStackTraceString() { 232 StringWriter sw = new StringWriter(); 233 PrintWriter pw = new PrintWriter(sw); 234 printStackTrace(pw); 235 pw.close(); 236 return sw.toString(); 237 } 238 239 /** 240 * For compatibility only. 241 */ 242 @Deprecated 243 public int getReturnCode() { 244 return super.getResponse().getStatus(); 245 } 246 247 /** 248 * Handle if needed custom error webengine module handler. 249 */ 250 public Response toResponse() { 251 WebContext ctx = WebEngine.getActiveContext(); 252 if (ctx != null && ctx.head() instanceof ModuleResource) { 253 return toWebModuleResponse(ctx); 254 } 255 return Response.status(status).entity(this).build(); 256 } 257 258 /** 259 * Ask Webengine module for custom error handling and response. 260 */ 261 protected Response toWebModuleResponse(WebContext ctx) { 262 ModuleResource mr = (ModuleResource) ctx.head(); 263 Object result = mr.handleError((WebApplicationException) this.getCause()); 264 if (result instanceof Response) { 265 return (Response) result; 266 } 267 if (result instanceof WebException) { 268 status = ((WebException) result).getStatus(); 269 } 270 return Response.fromResponse(getResponse()).status(status).entity(result).build(); 271 } 272 273 public int getStatus() { 274 return status; 275 } 276 277 public void setStatus(int status) { 278 this.status = status; 279 } 280 281 public String getType() { 282 return type; 283 } 284 285 public Throwable getCause() { 286 return cause; 287 } 288 289 public String getRequestId() { 290 return ""; 291 } 292 293 public String getHelpUrl() { 294 return ""; 295 } 296 297}