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