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.shared.context;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Queue;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  
30  import javax.el.ELException;
31  import javax.faces.FacesException;
32  import javax.faces.context.ExceptionHandler;
33  import javax.faces.context.ExternalContext;
34  import javax.faces.context.FacesContext;
35  import javax.faces.context.PartialResponseWriter;
36  import javax.faces.context.PartialViewContext;
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  /**
43   * 
44   * Specialized Ajax Handler, according to JSF 2.0 rev A section 13.3.7.
45   * 
46   * @author Leonardo Uribe
47   *
48   */
49  public class AjaxExceptionHandlerImpl extends ExceptionHandler
50  {
51      //The spec says this related to exception handling in ajax requests:
52      //
53      //"... JavaServer Faces Ajax frameworks must ensure exception information 
54      // is written to the response in the format: 
55      //
56      //<partial-response>
57      //  <error>
58      //    <error-name>...</error-name>
59      //    <error-message>...</error-message>
60      //  </error>
61      //</partial-response>
62      //
63      //Implementations must ensure that an ExceptionHandler suitable for writing 
64      //exceptions to the partial response is installed if the current request 
65      //required an Ajax response (PartialViewContext.isAjaxRequest() returns true)
66      //
67      //Implementations may choose to include a specialized ExceptionHandler for 
68      //Ajax that extends from javax.faces.context.ExceptionHandlerWrapper, and 
69      //have the javax.faces.context.ExceptionHandlerFactory implementation 
70      //install it if the environment requires it. ..."
71      
72      private static final Logger log = Logger.getLogger(AjaxExceptionHandlerImpl.class.getName());
73      
74      private Queue<ExceptionQueuedEvent> handled;
75      private Queue<ExceptionQueuedEvent> unhandled;
76      private ExceptionQueuedEvent handledAndThrown;
77      
78      public AjaxExceptionHandlerImpl()
79      {
80      }
81      
82      /**
83       * {@inheritDoc}
84       */
85      @Override
86      public ExceptionQueuedEvent getHandledExceptionQueuedEvent()
87      {
88          return handledAndThrown;
89      }
90  
91      /**
92       * {@inheritDoc}
93       */
94      @Override
95      public Iterable<ExceptionQueuedEvent> getHandledExceptionQueuedEvents()
96      {
97          return handled == null ? Collections.<ExceptionQueuedEvent>emptyList() : handled;
98      }
99  
100     /**
101      * {@inheritDoc}
102      */
103     @Override
104     public Throwable getRootCause(Throwable t)
105     {
106         if (t == null)
107         {
108             throw new NullPointerException("t");
109         }
110         
111         while (t != null)
112         {
113             Class<?> clazz = t.getClass();
114             if (!clazz.equals(FacesException.class) && !clazz.equals(ELException.class))
115             {
116                 return t;
117             }
118             
119             t = t.getCause();
120         }
121         
122         return null;
123     }
124 
125     /**
126      * {@inheritDoc}
127      */
128     @Override
129     public Iterable<ExceptionQueuedEvent> getUnhandledExceptionQueuedEvents()
130     {
131         return unhandled == null ? Collections.<ExceptionQueuedEvent>emptyList() : unhandled;
132     }
133 
134     /**
135      * {@inheritDoc}
136      */
137     @Override
138     public void handle() throws FacesException
139     {
140         if (unhandled != null && !unhandled.isEmpty())
141         {
142             if (handled == null)
143             {
144                 handled = new LinkedList<ExceptionQueuedEvent>();
145             }
146             
147             List<Throwable> throwableList = new ArrayList<Throwable>();
148             FacesContext facesContext = null;
149             
150             do
151             {
152                 // For each ExceptionEvent in the list
153                 
154                 // get the event to handle
155                 ExceptionQueuedEvent event = unhandled.peek();
156                 try
157                 {
158                     // call its getContext() method
159                     ExceptionQueuedEventContext context = event.getContext();
160 
161                     if (facesContext == null)
162                     {
163                         facesContext = event.getContext().getContext();
164                     }
165                     
166                     // and call getException() on the returned result
167                     Throwable exception = context.getException();
168                     
169                     // Upon encountering the first such Exception that is not an instance of
170                     // javax.faces.event.AbortProcessingException
171                     if (!shouldSkip(exception))
172                     {
173                         // set handledAndThrown so that getHandledExceptionQueuedEvent() returns this event
174                         handledAndThrown = event;
175                         
176                         Throwable rootCause = getRootCause(exception);
177                         
178                         throwableList.add(rootCause == null ? exception : rootCause);
179                         
180                         //break;
181                     }
182                     else
183                     {
184                         // Testing mojarra it logs a message and the exception
185                         // however, this behaviour is not mentioned in the spec
186                         log.log(Level.SEVERE, exception.getClass().getName() + " occured while processing " +
187                                 (context.inBeforePhase() ? "beforePhase() of " : 
188                                         (context.inAfterPhase() ? "afterPhase() of " : "")) + 
189                                 "phase " + context.getPhaseId() + ": " +
190                                 "UIComponent-ClientId=" + 
191                                 (context.getComponent() != null ? 
192                                         context.getComponent().getClientId(context.getContext()) : "") + ", " +
193                                 "Message=" + exception.getMessage());
194                         
195                         log.log(Level.SEVERE, exception.getMessage(), exception);
196                     }
197                 }
198                 finally
199                 {
200                     // if we will throw the Exception or if we just logged it,
201                     // we handled it in either way --> add to handled
202                     handled.add(event);
203                     unhandled.remove(event);
204                 }
205             } while (!unhandled.isEmpty());
206 
207             if (!throwableList.isEmpty())
208             {
209                 PartialResponseWriter partialWriter = null;
210                 boolean responseComplete = false;
211 
212                 //Retrieve the partial writer used to render the exception
213                 if (facesContext == null)
214                 {
215                     facesContext = FacesContext.getCurrentInstance();
216                 }
217                 
218                 facesContext = (facesContext == null) ? FacesContext.getCurrentInstance() : facesContext;
219                 ExternalContext externalContext = facesContext.getExternalContext();
220                 
221                 //Clean up the response if by some reason something has been written before.
222                 if (!facesContext.getExternalContext().isResponseCommitted())
223                 {
224                     facesContext.getExternalContext().responseReset();
225                 }
226                 
227                 PartialViewContext partialViewContext = facesContext.getPartialViewContext();
228                 partialWriter = partialViewContext.getPartialResponseWriter();
229 
230                 // ajax request --> xml error page 
231                 externalContext.setResponseContentType("text/xml");
232                 externalContext.setResponseCharacterEncoding("UTF-8");
233                 externalContext.addResponseHeader("Cache-control", "no-cache");
234                 
235                 try
236                 {
237                     partialWriter.startDocument();
238     
239                     for (Throwable t : throwableList)
240                     {
241                         renderAjaxError(partialWriter, t);
242                     }
243                     
244                     responseComplete = true;
245                 }
246                 catch (IOException e)
247                 {
248                     if (log.isLoggable(Level.SEVERE))
249                     {
250                         log.log(Level.SEVERE, "Cannot render exception on ajax request", e);
251                     }
252                 }
253                 finally
254                 {
255                     if (responseComplete)
256                     {
257                         try
258                         {
259                             partialWriter.endDocument();
260                             facesContext.responseComplete();
261                         }
262                         catch (IOException e1)
263                         {
264                             if (log.isLoggable(Level.SEVERE))
265                             {
266                                 log.log(Level.SEVERE, "Cannot render exception on ajax request", e1);
267                             }
268                         }
269                     }
270                 }
271             }
272         }
273     }
274     
275     private void renderAjaxError(PartialResponseWriter partialWriter, Throwable ex) throws IOException
276     {
277         partialWriter.startError(ex.getClass().getName());
278         if (ex.getCause() != null)
279         {
280             partialWriter.write(ex.getCause().toString());
281         }
282         else if (ex.getMessage() != null)
283         {
284             partialWriter.write(ex.getMessage());
285         }
286         partialWriter.endError();
287     }
288 
289     /**
290      * {@inheritDoc}
291      */
292     @Override
293     public boolean isListenerForSource(Object source)
294     {
295         return source instanceof ExceptionQueuedEventContext;
296     }
297 
298     /**
299      * {@inheritDoc}
300      */
301     @Override
302     public void processEvent(SystemEvent exceptionQueuedEvent) throws AbortProcessingException
303     {
304         if (unhandled == null)
305         {
306             unhandled = new LinkedList<ExceptionQueuedEvent>();
307         }
308         
309         unhandled.add((ExceptionQueuedEvent)exceptionQueuedEvent);
310     }
311     
312     protected Throwable getRethrownException(Throwable exception)
313     {
314         // Let toRethrow be either the result of calling getRootCause() on the Exception, 
315         // or the Exception itself, whichever is non-null
316         Throwable toRethrow = getRootCause(exception);
317         if (toRethrow == null)
318         {
319             toRethrow = exception;
320         }
321         
322         return toRethrow;
323     }
324     
325     protected FacesException wrap(Throwable exception)
326     {
327         if (exception instanceof FacesException)
328         {
329             return (FacesException) exception;
330         }
331         return new FacesException(exception);
332     }
333     
334     protected boolean shouldSkip(Throwable exception)
335     {
336         return exception instanceof AbortProcessingException;
337     }
338 
339 }