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}