View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.tomahawk.util;
20  
21  import java.io.IOException;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import javax.faces.FacesException;
28  import javax.faces.FactoryFinder;
29  import javax.faces.application.NavigationHandler;
30  import javax.faces.context.FacesContext;
31  import javax.faces.el.ValueBinding;
32  import javax.faces.lifecycle.Lifecycle;
33  import javax.faces.lifecycle.LifecycleFactory;
34  import javax.faces.webapp.FacesServlet;
35  import javax.servlet.ServletException;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  
40  /**
41   * This handler redirect to a jsf page when an error occurs.
42   * <p>
43   * This class is set as a config-parameter org.apache.myfaces.ERROR_HANDLER
44   * available on myfaces core jsf. (This does not work with RI)
45   * </p>
46   * <p>
47   * The idea is extends myfaces error handling feature, making possible to redirect
48   * to a jsf page when an error occur, using navigation rules.
49   * </p>
50   * <p>
51   * If this handler is not able to handle the error, and alternate error handler
52   * could be set in the config-parameter org.apache.myfaces.ERROR_REDIRECT_ALTERNATE_HANDLER
53   * </p>
54   * <p>
55   * The info of the error in the jsf page can be found using:
56   * </p>
57   * <ul>
58   * <li>#{exceptionContext.cause} : Cause retrieved from the exception</li>
59   * <li>#{exceptionContext.stackTrace} : Stack trace of the exception</li>
60   * <li>#{exceptionContext.exception} : Exception handled by this page </li>
61   * <li>#{exceptionContext.tree} : Print the component tree of the page that cause the error</li>
62   * <li>#{exceptionContext.vars} : Enviroment variables from the request</li>
63   * </ul>
64   * 
65   * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
66   * @version $Revision: 689684 $ $Date: 2008-08-27 19:50:09 -0500 (Wed, 27 Aug 2008) $
67   */
68  public class ErrorRedirectJSFPageHandler
69  {
70      private static final Log log = LogFactory
71              .getLog(ErrorRedirectJSFPageHandler.class);
72  
73      /**
74       * This param tells if ignore java.lang.* superclass names errors
75       * are found. In other words, all java.lang.* exceptions are simply
76       * skipped by this handler (so it could be thrown to an alternate handler
77       * or the jsp handler).
78       */
79      private static final String ERROR_HANDLING_IGNORE_BASE_ERROR_REDIRECT = "org.apache.myfaces.ERROR_HANDLING_IGNORE_BASE_ERROR_REDIRECT";
80         
81      private static final String ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER = "org.apache.myfaces.ERROR_REDIRECT_ALTERNATE_HANDLER";
82      
83      private static String getErrorRedirectAlternateHandler(FacesContext context)
84      {
85          String errorHandlerClass = context.getExternalContext().getInitParameter(ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER);
86          return errorHandlerClass;
87      }
88      
89      private static boolean isIgnoreBaseErrorRedirect(FacesContext context)
90      {
91          return getBooleanValue(context.getExternalContext().getInitParameter(
92                  ERROR_HANDLING_IGNORE_BASE_ERROR_REDIRECT), false);
93      }
94  
95      private static boolean getBooleanValue(String initParameter,
96              boolean defaultVal)
97      {
98          if (initParameter == null || initParameter.trim().length() == 0)
99              return defaultVal;
100 
101         return (initParameter.equalsIgnoreCase("on")
102                 || initParameter.equals("1") || initParameter
103                 .equalsIgnoreCase("true"));
104     }
105     
106     public static void handleException(FacesContext facesContext, Exception e)
107             throws ServletException, IOException
108     {
109         boolean oldRenderResponse = facesContext.getRenderResponse();
110         ExceptionContext exceptionContext = new ExceptionContext(e,
111                 facesContext.getViewRoot());
112 
113         if (!handleCauseOrThrowable(facesContext, exceptionContext, e,
114                 oldRenderResponse, isIgnoreBaseErrorRedirect(facesContext)))
115         {
116             //ErrorPageWriter.handleException(facesContext, ex);            
117             String errorHandlerClass = getErrorRedirectAlternateHandler(facesContext);
118             if (errorHandlerClass == null)
119             {
120                 //Redirect to another handler if it is available
121                 try
122                 {
123                     Class clazz = Class.forName(errorHandlerClass);
124 
125                     Object errorHandler = clazz.newInstance();
126 
127                     Method m = clazz.getMethod("handleException", new Class[]{FacesContext.class,Exception.class});
128                     m.invoke(errorHandler, new Object[]{facesContext, e});
129                 }
130                 catch(ClassNotFoundException ex)
131                 {
132                     throw new ServletException("Error-Handler : " 
133                             +errorHandlerClass
134                             + " was not found. Fix your web.xml-parameter : "+ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
135                 }
136                 catch (IllegalAccessException ex)
137                 {
138                     throw new ServletException("Constructor of error-Handler : " 
139                             +errorHandlerClass+ " is not accessible. Error-Handler is specified in web.xml-parameter : "
140                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
141                 }
142                 catch (InstantiationException ex)
143                 {
144                     throw new ServletException("Error-Handler : " 
145                             +errorHandlerClass
146                             + " could not be instantiated. Error-Handler is specified in web.xml-parameter : "
147                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
148                 }
149                 catch (NoSuchMethodException ex)
150                 {
151                     log.error("Error-Handler : " 
152                             +errorHandlerClass
153                             + " did not have a method with name : handleException and parameters : javax.faces.context.FacesContext, java.lang.Exception. Error-Handler is specified in web.xml-parameter : "
154                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
155                     //Try to look if it is implemented more general method handleThrowable
156                     handleThrowable(facesContext, ex);
157                 }
158                 catch (InvocationTargetException ex)
159                 {
160                     throw new ServletException("Excecution of method handleException in Error-Handler : " 
161                             +errorHandlerClass
162                             + " caused an exception. Error-Handler is specified in web.xml-parameter : "
163                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
164                 }                
165             }
166             else
167             {
168                 //throw exception because this redirect handler
169                 //does not have any rule defined.
170                 ErrorPageWriter.throwException(e);
171             }
172         }
173     }
174 
175     public static void handleThrowable(FacesContext facesContext, Throwable e)
176             throws ServletException, IOException
177     {
178         boolean oldRenderResponse = facesContext.getRenderResponse();
179         ExceptionContext exceptionContext = new ExceptionContext(e,
180                 facesContext.getViewRoot());
181 
182         if (!handleCauseOrThrowable(facesContext, exceptionContext, e,
183                 oldRenderResponse, isIgnoreBaseErrorRedirect(facesContext)))
184         {
185             //ErrorPageWriter.handleThrowable(facesContext, e);
186             String errorHandlerClass = getErrorRedirectAlternateHandler(facesContext);
187             if (errorHandlerClass == null)
188             {
189                 try {
190                     Class clazz = Class.forName(errorHandlerClass);
191 
192                     Object errorHandler = clazz.newInstance();
193 
194                     Method m = clazz.getMethod("handleThrowable", new Class[]{FacesContext.class,Throwable.class});
195                     m.invoke(errorHandler, new Object[]{facesContext, e});
196                 }
197                 catch(ClassNotFoundException ex) {
198                     throw new ServletException("Error-Handler : " 
199                             +errorHandlerClass
200                             + " was not found. Fix your web.xml-parameter : "+ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
201                 } catch (IllegalAccessException ex) {
202                     throw new ServletException("Constructor of error-Handler : " 
203                             +errorHandlerClass
204                             + " is not accessible. Error-Handler is specified in web.xml-parameter : "
205                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
206                 } catch (InstantiationException ex) {
207                     throw new ServletException("Error-Handler : " 
208                             +errorHandlerClass
209                             + " could not be instantiated. Error-Handler is specified in web.xml-parameter : "
210                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
211                 } catch (NoSuchMethodException ex) {
212                     throw new ServletException("Error-Handler : " 
213                             +errorHandlerClass
214                             + " did not have a method with name : handleException and parameters : javax.faces.context.FacesContext, java.lang.Exception. Error-Handler is specified in web.xml-parameter : "
215                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
216                 } catch (InvocationTargetException ex) {
217                     throw new ServletException("Excecution of method handleException in Error-Handler : " 
218                             +errorHandlerClass
219                             + " threw an exception. Error-Handler is specified in web.xml-parameter : "
220                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
221                 }                
222             }
223             else
224             {
225                 //throw exception because this redirect handler
226                 //does not have any rule defined.
227                 ErrorPageWriter.throwException(e);
228             }
229         }
230     }
231 
232     public static void handleExceptionList(FacesContext facesContext,
233             List exceptionList) throws ServletException, IOException
234     {
235         boolean isHandleCauseOrThrowable = false;
236 
237         //Try to check if some exception on the list have a applicable
238         //navigation rule.
239         for (Iterator it = exceptionList.iterator(); it.hasNext();)
240         {
241             Exception ex = (Exception) it.next();
242 
243             boolean oldRenderResponse = facesContext.getRenderResponse();
244             ExceptionContext exceptionContext = new ExceptionContext(ex,
245                     facesContext.getViewRoot());
246 
247             isHandleCauseOrThrowable = handleCauseOrThrowable(facesContext,
248                     exceptionContext, ex, oldRenderResponse,
249                     isIgnoreBaseErrorRedirect(facesContext));
250 
251             if (isHandleCauseOrThrowable)
252                 break;
253         }
254 
255         if (!isHandleCauseOrThrowable)
256         {
257             //If no navigation happened, handle as exception using 
258             //the default ErrorPageWriter
259             //ErrorPageWriter.handleException(facesContext,
260             //        (Exception) exceptionList.get(0));
261             String errorHandlerClass = getErrorRedirectAlternateHandler(facesContext);
262             if(errorHandlerClass != null) {
263                 try {
264                     Class clazz = Class.forName(errorHandlerClass);
265 
266                     Object errorHandler = clazz.newInstance();
267 
268                     Method m = clazz.getMethod("handleExceptionList", new Class[]{FacesContext.class,Exception.class});
269                     m.invoke(errorHandler, new Object[]{facesContext, exceptionList});
270                 }
271                 catch(ClassNotFoundException ex) {
272                     throw new ServletException("Error-Handler : " 
273                             +errorHandlerClass
274                             + " was not found. Fix your web.xml-parameter : "
275                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
276                 } catch (IllegalAccessException ex) {
277                     throw new ServletException("Constructor of error-Handler : " 
278                             +errorHandlerClass
279                             + " is not accessible. Error-Handler is specified in web.xml-parameter : "
280                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
281                 } catch (InstantiationException ex) {
282                     throw new ServletException("Error-Handler : " 
283                             +errorHandlerClass
284                             + " could not be instantiated. Error-Handler is specified in web.xml-parameter : "
285                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
286                 } catch (NoSuchMethodException ex) {
287                     //Handle in the old way, since no custom method handleExceptionList found,
288                     //throwing the first FacesException on the list.
289                     throw (FacesException) exceptionList.get(0);
290                 } catch (InvocationTargetException ex) {
291                     throw new ServletException("Excecution of method handleException in Error-Handler : " 
292                             +errorHandlerClass
293                             + " threw an exception. Error-Handler is specified in web.xml-parameter : "
294                             +ERROR_REDIRECT_ALTERNATE_HANDLER_PARAMETER,ex);
295                 }
296             }
297             else
298             {
299                 ErrorPageWriter.throwException((Exception) exceptionList.get(0));
300             }
301         }
302     }
303 
304     private static String getLifecycleId(FacesContext context)
305     {
306         String lifecycleId = context.getExternalContext().getInitParameter(
307                 FacesServlet.LIFECYCLE_ID_ATTR);
308         return lifecycleId != null ? lifecycleId
309                 : LifecycleFactory.DEFAULT_LIFECYCLE;
310     }
311 
312     private static boolean handleCauseOrThrowable(FacesContext context,
313             ExceptionContext exceptionContext, Throwable th,
314             boolean oldRenderResponse, boolean ignoreBaseException)
315     {
316 
317         boolean throwableHandled = false;
318         //First try to find the most inner child in the exception stack
319         if (th.getCause() != null)
320         {
321             throwableHandled = handleCauseOrThrowable(context,
322                     exceptionContext, th.getCause(), oldRenderResponse,
323                     ignoreBaseException);
324         }
325 
326         if (throwableHandled == false)
327         {
328             Class thClass = th.getClass();
329             while (thClass != null)
330             {
331                 // ignore java.lang.* superclass names
332                 if (ignoreBaseException
333                         && thClass.getName().startsWith("java.lang."))
334                 {
335                     return false;
336                 }
337                 log.info("Trying to redirect to jsf page:" + thClass.getName());
338                 NavigationHandler nh = context.getApplication()
339                         .getNavigationHandler();
340                 nh.handleNavigation(context, null, thClass.getName());
341 
342                 //Check if the navigation happened.
343                 //to call the render response phase
344                 if (context.getRenderResponse())//navigationHappened(oldRenderResponse, oldViewId, context)
345                 {
346                     //exceptionHandlingContext.setExceptionHandled();
347                     log.error("an error occurred : ", th);
348 
349                     ValueBinding vb = context.getApplication()
350                             .createValueBinding("#{exceptionContext}");
351 
352                     vb.setValue(context, exceptionContext);
353 
354                     LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder
355                             .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
356                     Lifecycle _lifecycle = lifecycleFactory
357                             .getLifecycle(getLifecycleId(context));
358                     _lifecycle.render(context);
359 
360                     vb.setValue(context, null);
361 
362                     context.responseComplete();
363                     return true;
364                 }
365                 thClass = thClass.getSuperclass();
366             }
367         }
368         return throwableHandled;
369     }
370 }