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