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 }