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