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}