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}