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 javax.faces.webapp;
20  
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.util.Collections;
24  import java.util.LinkedList;
25  import java.util.Queue;
26  import java.util.logging.Level;
27  import java.util.logging.Logger;
28  
29  import javax.el.ELException;
30  import javax.faces.FacesException;
31  import javax.faces.application.FacesMessage;
32  import javax.faces.component.UIComponent;
33  import javax.faces.component.UpdateModelException;
34  import javax.faces.context.ExceptionHandler;
35  import javax.faces.context.ExceptionHandlerFactory;
36  import javax.faces.context.FacesContext;
37  import javax.faces.event.AbortProcessingException;
38  import javax.faces.event.ExceptionQueuedEvent;
39  import javax.faces.event.ExceptionQueuedEventContext;
40  import javax.faces.event.SystemEvent;
41  
42  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
43  
44  /**
45   * @author Simon Lessard (latest modification by $Author: bommel $)
46   * @author Jakob Korherr
47   * @version $Revision: 1187700 $ $Date: 2011-10-22 07:19:37 -0500 (Sat, 22 Oct 2011) $
48   *
49   * @since 2.0
50   */
51  public class PreJsf2ExceptionHandlerFactory extends ExceptionHandlerFactory
52  {
53      //private static final Log log = LogFactory.getLog (PreJsf2ExceptionHandlerFactory.class);
54      private static final Logger log = Logger.getLogger(PreJsf2ExceptionHandlerFactory.class.getName());
55      
56      /**
57       * 
58       */
59      public PreJsf2ExceptionHandlerFactory()
60      {
61      }
62  
63      /**
64       * {@inheritDoc}
65       */
66      @Override
67      public ExceptionHandler getExceptionHandler()
68      {
69          return new PreJsf2ExceptionHandlerImpl();
70      }
71      
72      private static class PreJsf2ExceptionHandlerImpl extends ExceptionHandler
73      {
74          /*
75           * PLEASE NOTE!!!
76           * This is a copy of the ExceptionHandler implementation of myfaces-impl
77           * (org.apache.myfaces.context.ExceptionHandlerImpl), only handle() differs a bit.
78           * Any changes made here should also be applied to ExceptionHandlerImpl in the right way.
79           * 
80           * This is really ugly, but I think we have to do this due to the fact that PreJsf2ExceptionHandlerFactory
81           * can be declared directly as an exception handler factory, so it's not as if we can make the methods
82           * in the factory abstract and have a concrete impl in the impl project (and therefore be able to
83           * extend ExceptionHandlerImpl).  If this is not the case, please change accordingly.
84           */
85          
86          private static final Logger log = Logger.getLogger(PreJsf2ExceptionHandlerImpl.class.getName());
87          
88          /**
89           * Since JSF 2.0 there is a standard way to deal with unexpected Exceptions: the ExceptionHandler.
90           * Due to backwards compatibility MyFaces 2.0 also supports the init parameter 
91           * org.apache.myfaces.ERROR_HANDLER, introduced in MyFaces 1.2.4. However, the given error handler
92           * now only needs to include the following method:
93           * <ul>
94           * <li>handleException(FacesContext fc, Exception ex)</li>
95           * </ul>
96           * Furthermore, the init parameter only works when using the PreJsf2ExceptionHandlerFactory.
97           */
98          @JSFWebConfigParam(since="1.2.4",desc="Deprecated: use JSF 2.0 ExceptionHandler", deprecated=true)
99          private static final String ERROR_HANDLER_PARAMETER = "org.apache.myfaces.ERROR_HANDLER";
100         
101         private Queue<ExceptionQueuedEvent> handled;
102         private Queue<ExceptionQueuedEvent> unhandled;
103         private ExceptionQueuedEvent handledAndThrown;
104 
105         public PreJsf2ExceptionHandlerImpl()
106         {
107         }
108         
109         /**
110          * {@inheritDoc}
111          */
112         @Override
113         public ExceptionQueuedEvent getHandledExceptionQueuedEvent()
114         {
115             return handledAndThrown;
116         }
117 
118         /**
119          * {@inheritDoc}
120          */
121         @Override
122         public Iterable<ExceptionQueuedEvent> getHandledExceptionQueuedEvents()
123         {
124             return handled == null ? Collections.<ExceptionQueuedEvent>emptyList() : handled;
125         }
126 
127         /**
128          * {@inheritDoc}
129          */
130         @Override
131         public Throwable getRootCause(Throwable t)
132         {
133             if (t == null)
134             {
135                 throw new NullPointerException("t");
136             }
137             
138             while (t != null)
139             {
140                 Class<?> clazz = t.getClass();
141                 if (!clazz.equals(FacesException.class) && !clazz.equals(ELException.class))
142                 {
143                     return t;
144                 }
145                 
146                 t = t.getCause();
147             }
148             
149             return null;
150         }
151 
152         /**
153          * {@inheritDoc}
154          */
155         @Override
156         public Iterable<ExceptionQueuedEvent> getUnhandledExceptionQueuedEvents()
157         {
158             return unhandled == null ? Collections.<ExceptionQueuedEvent>emptyList() : unhandled;
159         }
160 
161         /**
162          * {@inheritDoc}
163          * 
164          * Differs from ExceptionHandlerImpl.handle() in three points:
165          *  - Any exceptions thrown before or after phase execution will be logged and swallowed.
166          *  - If the Exception is an instance of UpdateModelException, extract the FacesMessage from the UpdateModelException.
167          *    Log a SEVERE message to the log and queue the FacesMessage on the FacesContext, using the clientId of the source
168          *    component in a call to FacesContext.addMessage(java.lang.String, javax.faces.application.FacesMessage).
169          *  - Checks org.apache.myfaces.ERROR_HANDLER for backwards compatibility to myfaces-1.2's error handling
170          */
171         @Override
172         public void handle() throws FacesException
173         {
174             if (unhandled != null && !unhandled.isEmpty())
175             {
176                 if (handled == null)
177                 {
178                     handled = new LinkedList<ExceptionQueuedEvent>();
179                 }
180                 
181                 // check the org.apache.myfaces.ERROR_HANDLER init param 
182                 // for backwards compatibility to myfaces-1.2's error handling
183                 String errorHandlerClass = FacesContext.getCurrentInstance()
184                         .getExternalContext().getInitParameter(ERROR_HANDLER_PARAMETER);
185                 
186                 FacesException toThrow = null;
187                 
188                 do
189                 {
190                     // For each ExceptionEvent in the list
191                     
192                     // get the event to handle
193                     ExceptionQueuedEvent event = unhandled.peek();
194                     try
195                     {
196                         // call its getContext() method
197                         ExceptionQueuedEventContext context = event.getContext();
198                         
199                         // and call getException() on the returned result
200                         Throwable exception = context.getException();
201                         
202                         if (errorHandlerClass != null)
203                         {
204                             // myfaces-1.2's error handler
205                             try
206                             {
207                                 Class<?> clazz = Class.forName(errorHandlerClass);
208 
209                                 Object errorHandler = clazz.newInstance();
210 
211                                 Method m = clazz.getMethod("handleException", new Class[] { FacesContext.class, Exception.class });
212                                 m.invoke(errorHandler, new Object[] { context.getContext(), exception });
213                             }
214                             catch (ClassNotFoundException ex)
215                             {
216                                 throw new FacesException("Error-Handler : " + errorHandlerClass
217                                         + " was not found. Fix your web.xml-parameter : " + ERROR_HANDLER_PARAMETER, ex);
218                             }
219                             catch (IllegalAccessException ex)
220                             {
221                                 throw new FacesException("Constructor of error-Handler : " + errorHandlerClass
222                                         + " is not accessible. Error-Handler is specified in web.xml-parameter : "
223                                         + ERROR_HANDLER_PARAMETER, ex);
224                             }
225                             catch (InstantiationException ex)
226                             {
227                                 throw new FacesException("Error-Handler : " + errorHandlerClass
228                                         + " could not be instantiated. Error-Handler is specified in web.xml-parameter : "
229                                         + ERROR_HANDLER_PARAMETER, ex);
230                             }
231                             catch (NoSuchMethodException ex)
232                             {
233                                 throw new FacesException("Error-Handler : " + errorHandlerClass
234                                         + " does not have a method with name : handleException and parameters : "
235                                         + "javax.faces.context.FacesContext, java.lang.Exception. Error-Handler is"
236                                         + "specified in web.xml-parameter : " + ERROR_HANDLER_PARAMETER, ex);
237                             }
238                             catch (InvocationTargetException ex)
239                             {
240                                 throw new FacesException("Excecution of method handleException in Error-Handler : "
241                                         + errorHandlerClass
242                                         + " caused an exception. Error-Handler is specified in web.xml-parameter : "
243                                         + ERROR_HANDLER_PARAMETER, ex);
244                             }
245                         }
246                         else
247                         {
248                             // spec described behaviour of PreJsf2ExceptionHandler
249                             
250                             // UpdateModelException needs special treatment here
251                             if (exception instanceof UpdateModelException)
252                             {
253                                 FacesMessage message = ((UpdateModelException) exception).getFacesMessage();
254                                 // Log a SEVERE message to the log
255                                 log.log(Level.SEVERE, message.getSummary(), exception.getCause());
256                                 // queue the FacesMessage on the FacesContext
257                                 UIComponent component = context.getComponent();
258                                 String clientId = null;
259                                 if (component != null)
260                                 {
261                                     clientId = component.getClientId(context.getContext());
262                                 }
263                                 context.getContext().addMessage(clientId, message);
264                             }
265                             else if (!shouldSkip(exception) && !context.inBeforePhase() && !context.inAfterPhase())
266                             {
267                                 // set handledAndThrown so that getHandledExceptionQueuedEvent() returns this event
268                                 handledAndThrown = event;
269                                 
270                                 // Re-wrap toThrow in a ServletException or (PortletException, if in a portlet environment) 
271                                 // and throw it
272                                 // FIXME: The spec says to NOT use a FacesException to propagate the exception, but I see
273                                 //        no other way as ServletException is not a RuntimeException
274                                 toThrow = wrap(getRethrownException(exception));
275                                 break;
276                             }
277                             else
278                             {
279                                 // Testing mojarra it logs a message and the exception
280                                 // however, this behaviour is not mentioned in the spec
281                                 log.log(Level.SEVERE, exception.getClass().getName() + " occured while processing " +
282                                         (context.inBeforePhase() ? "beforePhase() of " : 
283                                                 (context.inAfterPhase() ? "afterPhase() of " : "")) + 
284                                         "phase " + context.getPhaseId() + ": " +
285                                         "UIComponent-ClientId=" + 
286                                         (context.getComponent() != null ? 
287                                                 context.getComponent().getClientId(context.getContext()) : "") + ", " +
288                                         "Message=" + exception.getMessage());
289                                 
290                                 log.log(Level.SEVERE, exception.getMessage(), exception);
291                             }
292                         }
293                     }
294                     catch (Throwable t)
295                     {
296                         // A FacesException must be thrown if a problem occurs while performing
297                         // the algorithm to handle the exception
298                         throw new FacesException("Could not perform the algorithm to handle the Exception", t);
299                     }
300                     finally
301                     {
302                         // if we will throw the Exception or if we just logged it,
303                         // we handled it in either way --> add to handled
304                         handled.add(event);
305                         unhandled.remove(event);
306                     }
307                 } while (!unhandled.isEmpty());
308                 
309                 // do we have to throw an Exception?
310                 if (toThrow != null)
311                 {
312                     throw toThrow;
313                 }
314             }
315         }
316 
317         /**
318          * {@inheritDoc}
319          */
320         @Override
321         public boolean isListenerForSource(Object source)
322         {
323             return source instanceof ExceptionQueuedEventContext;
324         }
325 
326         /**
327          * {@inheritDoc}
328          */
329         @Override
330         public void processEvent(SystemEvent exceptionQueuedEvent) throws AbortProcessingException
331         {
332             if (unhandled == null)
333             {
334                 unhandled = new LinkedList<ExceptionQueuedEvent>();
335             }
336             
337             unhandled.add((ExceptionQueuedEvent)exceptionQueuedEvent);
338         }
339         
340         protected Throwable getRethrownException(Throwable exception)
341         {
342             // Let toRethrow be either the result of calling getRootCause() on the Exception, 
343             // or the Exception itself, whichever is non-null
344             Throwable toRethrow = getRootCause(exception);
345             if (toRethrow == null)
346             {
347                 toRethrow = exception;
348             }
349             
350             return toRethrow;
351         }
352         
353         protected FacesException wrap(Throwable exception)
354         {
355             if (exception instanceof FacesException)
356             {
357                 return (FacesException) exception;
358             }
359             return new FacesException(exception);
360         }
361         
362         protected boolean shouldSkip(Throwable exception)
363         {
364             return exception instanceof AbortProcessingException;
365         }
366     }
367 }