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: struberg $)
46   * @author Jakob Korherr
47   * @version $Revision: 1188565 $ $Date: 2011-10-25 03:38:53 -0500 (Tue, 25 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
167          *  FacesMessage from the UpdateModelException.
168          *    Log a SEVERE message to the log and queue the FacesMessage on the
169          *    FacesContext, using the clientId of the source
170          *    component in a call to FacesContext.addMessage(java.lang.String, javax.faces.application.FacesMessage).
171          *  - Checks org.apache.myfaces.ERROR_HANDLER for backwards compatibility to myfaces-1.2's error handling
172          */
173         @Override
174         public void handle() throws FacesException
175         {
176             if (unhandled != null && !unhandled.isEmpty())
177             {
178                 if (handled == null)
179                 {
180                     handled = new LinkedList<ExceptionQueuedEvent>();
181                 }
182                 
183                 // check the org.apache.myfaces.ERROR_HANDLER init param 
184                 // for backwards compatibility to myfaces-1.2's error handling
185                 String errorHandlerClass = FacesContext.getCurrentInstance()
186                         .getExternalContext().getInitParameter(ERROR_HANDLER_PARAMETER);
187                 
188                 FacesException toThrow = null;
189                 
190                 do
191                 {
192                     // For each ExceptionEvent in the list
193                     
194                     // get the event to handle
195                     ExceptionQueuedEvent event = unhandled.peek();
196                     try
197                     {
198                         // call its getContext() method
199                         ExceptionQueuedEventContext context = event.getContext();
200                         
201                         // and call getException() on the returned result
202                         Throwable exception = context.getException();
203                         
204                         if (errorHandlerClass != null)
205                         {
206                             // myfaces-1.2's error handler
207                             try
208                             {
209                                 Class<?> clazz = Class.forName(errorHandlerClass);
210 
211                                 Object errorHandler = clazz.newInstance();
212 
213                                 Method m = clazz.getMethod("handleException",
214                                                            new Class[] { FacesContext.class, Exception.class });
215                                 m.invoke(errorHandler, new Object[] { context.getContext(), exception });
216                             }
217                             catch (ClassNotFoundException ex)
218                             {
219                                 throw new FacesException("Error-Handler : " + errorHandlerClass
220                                         + " was not found. Fix your web.xml-parameter : "
221                                         + ERROR_HANDLER_PARAMETER, ex);
222                             }
223                             catch (IllegalAccessException ex)
224                             {
225                                 throw new FacesException("Constructor of error-Handler : " + errorHandlerClass
226                                         + " is not accessible. Error-Handler is specified in web.xml-parameter : "
227                                         + ERROR_HANDLER_PARAMETER, ex);
228                             }
229                             catch (InstantiationException ex)
230                             {
231                                 throw new FacesException("Error-Handler : " + errorHandlerClass
232                                     + " could not be instantiated. Error-Handler is specified in web.xml-parameter : "
233                                     + ERROR_HANDLER_PARAMETER, ex);
234                             }
235                             catch (NoSuchMethodException ex)
236                             {
237                                 throw new FacesException("Error-Handler : " + errorHandlerClass
238                                         + " does not have a method with name : handleException and parameters : "
239                                         + "javax.faces.context.FacesContext, java.lang.Exception. Error-Handler is"
240                                         + "specified in web.xml-parameter : " + ERROR_HANDLER_PARAMETER, ex);
241                             }
242                             catch (InvocationTargetException ex)
243                             {
244                                 throw new FacesException("Excecution of method handleException in Error-Handler : "
245                                         + errorHandlerClass
246                                         + " caused an exception. Error-Handler is specified in web.xml-parameter : "
247                                         + ERROR_HANDLER_PARAMETER, ex);
248                             }
249                         }
250                         else
251                         {
252                             // spec described behaviour of PreJsf2ExceptionHandler
253                             
254                             // UpdateModelException needs special treatment here
255                             if (exception instanceof UpdateModelException)
256                             {
257                                 FacesMessage message = ((UpdateModelException) exception).getFacesMessage();
258                                 // Log a SEVERE message to the log
259                                 log.log(Level.SEVERE, message.getSummary(), exception.getCause());
260                                 // queue the FacesMessage on the FacesContext
261                                 UIComponent component = context.getComponent();
262                                 String clientId = null;
263                                 if (component != null)
264                                 {
265                                     clientId = component.getClientId(context.getContext());
266                                 }
267                                 context.getContext().addMessage(clientId, message);
268                             }
269                             else if (!shouldSkip(exception) && !context.inBeforePhase() && !context.inAfterPhase())
270                             {
271                                 // set handledAndThrown so that getHandledExceptionQueuedEvent() returns this event
272                                 handledAndThrown = event;
273                                 
274                                 // Re-wrap toThrow in a ServletException or
275                                 // (PortletException, if in a portlet environment)
276                                 // and throw it
277                                 // FIXME: The spec says to NOT use a FacesException
278                                 // to propagate the exception, but I see
279                                 //        no other way as ServletException is not a RuntimeException
280                                 toThrow = wrap(getRethrownException(exception));
281                                 break;
282                             }
283                             else
284                             {
285                                 // Testing mojarra it logs a message and the exception
286                                 // however, this behaviour is not mentioned in the spec
287                                 log.log(Level.SEVERE, exception.getClass().getName() + " occured while processing " +
288                                         (context.inBeforePhase() ? "beforePhase() of " : 
289                                                 (context.inAfterPhase() ? "afterPhase() of " : "")) + 
290                                         "phase " + context.getPhaseId() + ": " +
291                                         "UIComponent-ClientId=" + 
292                                         (context.getComponent() != null ? 
293                                                 context.getComponent().getClientId(context.getContext()) : "") + ", " +
294                                         "Message=" + exception.getMessage());
295                                 
296                                 log.log(Level.SEVERE, exception.getMessage(), exception);
297                             }
298                         }
299                     }
300                     catch (Throwable t)
301                     {
302                         // A FacesException must be thrown if a problem occurs while performing
303                         // the algorithm to handle the exception
304                         throw new FacesException("Could not perform the algorithm to handle the Exception", t);
305                     }
306                     finally
307                     {
308                         // if we will throw the Exception or if we just logged it,
309                         // we handled it in either way --> add to handled
310                         handled.add(event);
311                         unhandled.remove(event);
312                     }
313                 } while (!unhandled.isEmpty());
314                 
315                 // do we have to throw an Exception?
316                 if (toThrow != null)
317                 {
318                     throw toThrow;
319                 }
320             }
321         }
322 
323         /**
324          * {@inheritDoc}
325          */
326         @Override
327         public boolean isListenerForSource(Object source)
328         {
329             return source instanceof ExceptionQueuedEventContext;
330         }
331 
332         /**
333          * {@inheritDoc}
334          */
335         @Override
336         public void processEvent(SystemEvent exceptionQueuedEvent) throws AbortProcessingException
337         {
338             if (unhandled == null)
339             {
340                 unhandled = new LinkedList<ExceptionQueuedEvent>();
341             }
342             
343             unhandled.add((ExceptionQueuedEvent)exceptionQueuedEvent);
344         }
345         
346         protected Throwable getRethrownException(Throwable exception)
347         {
348             // Let toRethrow be either the result of calling getRootCause() on the Exception, 
349             // or the Exception itself, whichever is non-null
350             Throwable toRethrow = getRootCause(exception);
351             if (toRethrow == null)
352             {
353                 toRethrow = exception;
354             }
355             
356             return toRethrow;
357         }
358         
359         protected FacesException wrap(Throwable exception)
360         {
361             if (exception instanceof FacesException)
362             {
363                 return (FacesException) exception;
364             }
365             return new FacesException(exception);
366         }
367         
368         protected boolean shouldSkip(Throwable exception)
369         {
370             return exception instanceof AbortProcessingException;
371         }
372     }
373 }