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
040/**
041 * @deprecated since 9.3. Use {@code NuxeoException} instead.
042 */
043@Deprecated
044public class WebException extends WebApplicationException {
045
046    protected static final Log log = LogFactory.getLog(WebException.class);
047
048    private static final long serialVersionUID = 176876876786L;
049
050    protected String type;
051
052    protected Throwable cause;
053
054    protected String message;
055
056    protected int status;
057
058    public WebException() {
059    }
060
061    public WebException(Response response) {
062        super(response);
063    }
064
065    public WebException(int status) {
066        super(status);
067        this.status = status;
068    }
069
070    public WebException(Response.Status status) {
071        super(status);
072        this.status = status.getStatusCode();
073    }
074
075    /**
076     * Use WebException.wrap() and not the constructor.
077     */
078    protected WebException(Throwable cause, Response.Status status) {
079        super(cause, status);
080        this.cause = cause;
081        this.status = status.getStatusCode();
082    }
083
084    protected WebException(Throwable cause, int status) {
085        super(cause, status);
086        this.cause = cause;
087        this.status = status;
088    }
089
090    protected WebException(Throwable cause) {
091        super(cause);
092        this.cause = cause;
093    }
094
095    public WebException(String message) {
096        this.message = message;
097    }
098
099    public WebException(String message, int code) {
100        super(code);
101        this.message = message;
102        this.status = code;
103    }
104
105    public WebException(String message, Throwable cause, int status) {
106        if (cause == null) {
107            throw new IllegalArgumentException("the cause parameter cannot be null");
108        }
109        this.status = status == -1 ? getStatus(cause) : status;
110        this.cause = cause;
111        this.message = message == null ? cause.getMessage() : message;
112        this.type = cause.getClass().getName();
113    }
114
115    public WebException(String message, Throwable cause) {
116        if (cause == null) {
117            throw new IllegalArgumentException("the cause parameter cannot be null");
118        }
119        this.status = getStatus(cause);
120        this.cause = cause;
121        this.message = message == null ? cause.getMessage() : message;
122        this.type = cause.getClass().getName();
123    }
124
125    public static WebException newException(String message, Throwable cause) {
126        return newException(message, cause, -1);
127    }
128
129    public static WebException newException(Throwable cause) {
130        return newException(null, cause);
131    }
132
133    public static WebException newException(String message, Throwable cause, int status) {
134        if (cause == null) {
135            throw new IllegalArgumentException("the cause parameter cannot be null");
136        }
137        return new WebException(message, cause, status);
138    }
139
140    public static String toString(Throwable t) {
141        StringWriter sw = new StringWriter();
142        PrintWriter pw = new PrintWriter(sw);
143        t.printStackTrace(pw);
144        pw.close();
145        return sw.toString();
146    }
147
148    public static WebException wrap(Throwable e) {
149        return wrap(null, e);
150    }
151
152    public static WebException wrap(String message, Throwable exception) {
153        if (exception instanceof Exception) {
154            ExceptionUtils.checkInterrupt((Exception) exception);
155        }
156        if (exception instanceof DocumentSecurityException) {
157            return new WebException(message, exception, Response.Status.FORBIDDEN.getStatusCode());
158        } else if (exception instanceof WebException) {
159            return (WebException) exception;
160        } else if (exception instanceof DocumentNotFoundException) {
161            return new WebException(message, exception, Response.Status.NOT_FOUND.getStatusCode());
162        } else {
163            return new WebException(message, exception);
164        }
165    }
166
167    public static Object handleError(WebApplicationException e) {
168        StringWriter sw = new StringWriter();
169        PrintWriter pw = new PrintWriter(sw);
170        e.printStackTrace(pw);
171        pw.close();
172        return Response.status(500).entity(sw.toString()).build();
173    }
174
175    /**
176     * Tries to find the best matching HTTP status for the given exception.
177     */
178    public static int getStatus(Throwable cause) {
179        // use a max depth of 8 to avoid infinite loops for broken exceptions
180        // which are referencing themselves as the cause
181        return getStatus(cause, 8);
182    }
183
184    public static int getStatus(Throwable cause, int depth) {
185        if (depth == 0) {
186            log.warn("Possible infinite loop! Check the exception wrapping.");
187            return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
188        }
189        if ((cause instanceof DocumentSecurityException) || (cause instanceof SecurityException)
190                || "javax.ejb.EJBAccessException".equals(cause.getClass().getName())) {
191            return HttpServletResponse.SC_FORBIDDEN;
192        } else if (cause instanceof DocumentNotFoundException || cause instanceof WebResourceNotFoundException) {
193            return HttpServletResponse.SC_NOT_FOUND;
194        } else if (cause instanceof WebSecurityException) {
195            return HttpServletResponse.SC_UNAUTHORIZED;
196        }
197        Throwable parent = cause.getCause();
198        if (parent == cause) {
199            log.warn("Infinite loop detected! Check the exception wrapping.");
200            return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
201        }
202        if (parent != null) {
203            return getStatus(parent, depth - 1);
204        }
205        if (cause.getMessage() != null && 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}