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