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            return HttpServletResponse.SC_FORBIDDEN;
191        } else if (cause instanceof DocumentNotFoundException || cause instanceof WebResourceNotFoundException) {
192            return HttpServletResponse.SC_NOT_FOUND;
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 && cause.getMessage().contains(DocumentNotFoundException.class.getName())) {
205            log.warn("Badly wrapped exception: found a DocumentNotFoundException"
206                    + " message but no DocumentNotFoundException", cause);
207            return HttpServletResponse.SC_NOT_FOUND;
208        }
209        return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
210    }
211
212    public static boolean isSecurityError(Throwable t) {
213        return getStatus(t) == HttpServletResponse.SC_FORBIDDEN;
214    }
215
216    /**
217     * For compatibility only.
218     */
219    @Deprecated
220    public static Response toResponse(Throwable t) {
221        return new WebException(t).toResponse();
222    }
223
224    @Override
225    public String getMessage() {
226        return message;
227    }
228
229    public int getStatusCode() {
230        return status;
231    }
232
233    public String getStackTraceString() {
234        StringWriter sw = new StringWriter();
235        PrintWriter pw = new PrintWriter(sw);
236        printStackTrace(pw);
237        pw.close();
238        return sw.toString();
239    }
240
241    /**
242     * For compatibility only.
243     */
244    @Deprecated
245    public int getReturnCode() {
246        return super.getResponse().getStatus();
247    }
248
249    /**
250     * Handle if needed custom error webengine module handler.
251     */
252    public Response toResponse() {
253        WebContext ctx = WebEngine.getActiveContext();
254        if (ctx != null && ctx.head() instanceof ModuleResource) {
255            return toWebModuleResponse(ctx);
256        }
257        return Response.status(status).entity(this).build();
258    }
259
260    /**
261     * Ask Webengine module for custom error handling and response.
262     */
263    protected Response toWebModuleResponse(WebContext ctx) {
264        ModuleResource mr = (ModuleResource) ctx.head();
265        Object result = mr.handleError(this.getCause());
266        if (result instanceof Response) {
267            return (Response) result;
268        }
269        if (result instanceof WebException) {
270            status = ((WebException) result).getStatus();
271        }
272        return Response.fromResponse(getResponse()).status(status).entity(result).build();
273    }
274
275    public int getStatus() {
276        return status;
277    }
278
279    public void setStatus(int status) {
280        this.status = status;
281    }
282
283    public String getType() {
284        return type;
285    }
286
287    @Override
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}