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.io.IOException;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.InvocationTargetException;
24  import java.util.List;
25  
26  import javax.faces.FactoryFinder;
27  import javax.faces.FacesException;
28  import javax.faces.context.FacesContext;
29  import javax.faces.context.FacesContextFactory;
30  import javax.faces.lifecycle.Lifecycle;
31  import javax.faces.lifecycle.LifecycleFactory;
32  import javax.servlet.Servlet;
33  import javax.servlet.ServletConfig;
34  import javax.servlet.ServletException;
35  import javax.servlet.ServletRequest;
36  import javax.servlet.ServletResponse;
37  import javax.servlet.http.HttpServletRequest;
38  import javax.servlet.http.HttpServletResponse;
39  
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
43  
44  /**
45   * see Javadoc of <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a>
46   *
47   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
48   * @version $Revision: 888211 $ $Date: 2009-12-07 20:07:19 -0500 (Mon, 07 Dec 2009) $
49   */
50  public final class FacesServlet
51          implements Servlet
52  {
53      private static final Log log = LogFactory.getLog(FacesServlet.class);
54      
55      /**
56       * Comma separated list of URIs of (additional) faces config files.
57       * (e.g. /WEB-INF/my-config.xml)See JSF 1.0 PRD2, 10.3.2
58       * Attention: You do not need to put /WEB-INF/faces-config.xml in here.
59       */
60      @JSFWebConfigParam(since="1.1")
61      public static final String CONFIG_FILES_ATTR = "javax.faces.CONFIG_FILES";
62  
63      /**
64       * Identify the Lifecycle instance to be used.
65       */
66      @JSFWebConfigParam(since="1.1")
67      public static final String LIFECYCLE_ID_ATTR = "javax.faces.LIFECYCLE_ID";
68  
69      private static final String SERVLET_INFO = "FacesServlet of the MyFaces API implementation";
70      
71      /**
72       * Indicate if myfaces is responsible to handle errors. 
73       * See http://wiki.apache.org/myfaces/Handling_Server_Errors for details. 
74       */
75      @JSFWebConfigParam(defaultValue="true",expectedValues="true,false", since="1.2.4")
76      private static final String ERROR_HANDLING_PARAMETER = "org.apache.myfaces.ERROR_HANDLING";
77      
78      /**
79       * If you want to choose a different class for handling the exception.
80       * <p> 
81       * The error-handler needs to include the following methods:
82       * </p>
83       * <ul>
84       * <li>handleException(FacesContext fc, Exception ex)</li>
85       * <li>handleExceptionList(FacesContext facesContext, List exceptionList)</li>
86       * <li>handleThrowable(FacesContext facesContext, Throwable ex)</li>
87       * </ul>
88       */
89      @JSFWebConfigParam(since="1.2.4")
90      private static final String ERROR_HANDLER_PARAMETER = "org.apache.myfaces.ERROR_HANDLER";
91      private static final String ERROR_HANDLING_EXCEPTION_LIST = "org.apache.myfaces.errorHandling.exceptionList";
92  
93  
94      private ServletConfig _servletConfig;
95      private FacesContextFactory _facesContextFactory;
96      private Lifecycle _lifecycle;
97  
98      public FacesServlet()
99      {
100         super();
101     }
102 
103     public void destroy()
104     {
105         _servletConfig = null;
106         _facesContextFactory = null;
107         _lifecycle = null;
108         if(log.isTraceEnabled()) log.trace("destroy");
109     }
110 
111     public ServletConfig getServletConfig()
112     {
113         return _servletConfig;
114     }
115 
116     public String getServletInfo()
117     {
118         return SERVLET_INFO;
119     }
120 
121     private String getLifecycleId()
122     {
123         //1. check for Servlet's init-param
124         //2. check for global context parameter
125         //3. use default Lifecycle Id, if none of them was provided
126         String serLifecycleId = _servletConfig.getInitParameter(LIFECYCLE_ID_ATTR);
127         String appLifecycleId = _servletConfig.getServletContext().getInitParameter(LIFECYCLE_ID_ATTR);
128         appLifecycleId = serLifecycleId == null ? appLifecycleId : serLifecycleId;
129         return appLifecycleId != null ? appLifecycleId : LifecycleFactory.DEFAULT_LIFECYCLE;
130     }
131 
132     public void init(ServletConfig servletConfig)
133             throws ServletException
134     {
135         if(log.isTraceEnabled()) log.trace("init begin");
136         _servletConfig = servletConfig;
137         _facesContextFactory = (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
138         //TODO: null-check for Weblogic, that tries to initialize Servlet before ContextListener
139 
140         //Javadoc says: Lifecycle instance is shared across multiple simultaneous requests, it must be implemented in a thread-safe manner.
141         //So we can acquire it here once:
142         LifecycleFactory lifecycleFactory = (LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
143         _lifecycle = lifecycleFactory.getLifecycle(getLifecycleId());
144         if(log.isTraceEnabled()) log.trace("init end");
145     }
146 
147     public void service(ServletRequest request,
148                         ServletResponse response)
149             throws IOException,
150                    ServletException
151     {
152 
153         HttpServletRequest httpRequest = ((HttpServletRequest) request);
154         String pathInfo = httpRequest.getPathInfo();
155 
156         // if it is a prefix mapping ...
157         if (pathInfo != null
158                 && (pathInfo.startsWith("/WEB-INF") || pathInfo
159                         .startsWith("/META-INF")))
160         {
161             StringBuffer buffer = new StringBuffer();
162 
163             buffer.append(" Someone is trying to access a secure resource : ").append(pathInfo);
164             buffer.append("\n remote address is ").append(httpRequest.getRemoteAddr());
165             buffer.append("\n remote host is ").append(httpRequest.getRemoteHost());
166             buffer.append("\n remote user is ").append(httpRequest.getRemoteUser());
167             buffer.append("\n request URI is ").append(httpRequest.getRequestURI());
168 
169             log.warn(buffer.toString());
170 
171             // Why does RI return a 404 and not a 403, SC_FORBIDDEN ?
172 
173             ((HttpServletResponse) response)
174                     .sendError(HttpServletResponse.SC_NOT_FOUND);
175             return;
176         }
177 
178         if(log.isTraceEnabled()) log.trace("service begin");
179 
180         FacesContext facesContext = prepareFacesContext(request, response);
181 
182         try {
183             _lifecycle.execute(facesContext);
184 
185             if (!handleQueuedExceptions(facesContext))
186             {
187                 _lifecycle.render(facesContext);
188             }
189         }
190         catch (Exception e)
191         {
192             handleLifecycleException(facesContext, e);
193         }
194         catch (Throwable e)
195         {
196             handleLifecycleThrowable(facesContext, e);
197         }
198         finally
199         {
200             facesContext.release();
201         }
202         if(log.isTraceEnabled()) log.trace("service end");
203     }
204 
205     /**This method makes sure we see an exception page also
206      * if an exception has been thrown in UIInput.updateModel();
207      * In this method, according to the spec, we may not rethrow the
208      * exception, so we add it to a list and process it here.
209      *
210      * Attention: if you use redirects, the exceptions will get lost - exactly
211      * like in the case of FacesMessages. If you want them to be taken over to the
212      * next request, you should try the redirect-tracker of MyFaces.
213      *
214      * @param facesContext
215      * @throws FacesException
216      */
217     private boolean handleQueuedExceptions(FacesContext facesContext) throws IOException, ServletException {
218         List li = (List)
219                 facesContext.getExternalContext().getRequestMap().get(ERROR_HANDLING_EXCEPTION_LIST);
220 
221         if(li != null && li.size()>=1)  {
222             //todo: for now, we only handle the first exception out of the list - we just rethrow this
223             //first exception.
224             //in the end, we should enable the error handler to show all the exceptions at once
225             boolean errorHandling = getBooleanValue(facesContext.getExternalContext().getInitParameter(ERROR_HANDLING_PARAMETER), true);
226 
227             if(errorHandling) {
228                 String errorHandlerClass = facesContext.getExternalContext().getInitParameter(ERROR_HANDLER_PARAMETER);            
229                 if(errorHandlerClass != null) {
230                     try {
231                         Class clazz = Class.forName(errorHandlerClass);
232 
233                         Object errorHandler = clazz.newInstance();
234 
235                         Method m = clazz.getMethod("handleExceptionList", new Class[]{FacesContext.class,List.class});
236                         m.invoke(errorHandler, new Object[]{facesContext, li});
237                     }
238                     catch(ClassNotFoundException ex) {
239                         throw new ServletException("Error-Handler : " +errorHandlerClass+ " was not found. Fix your web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
240                     } catch (IllegalAccessException ex) {
241                         throw new ServletException("Constructor of error-Handler : " +errorHandlerClass+ " is not accessible. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
242                     } catch (InstantiationException ex) {
243                         throw new ServletException("Error-Handler : " +errorHandlerClass+ " could not be instantiated. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
244                     } catch (NoSuchMethodException ex) {
245                         //Handle in the old way, since no custom method handleExceptionList found,
246                         //throwing the first FacesException on the list.
247                         throw (FacesException) li.get(0);
248                     } catch (InvocationTargetException ex) {
249                         throw new ServletException("Excecution of method handleException in Error-Handler : " +errorHandlerClass+ " threw an exception. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
250                     }
251                 }
252                 else {
253                     _ErrorPageWriter.handleExceptionList(facesContext, li);
254                 }
255             }
256             else {
257                 _ErrorPageWriter.throwException((Exception) li.get(0));
258             }
259             return true;
260         }
261         return false;
262     }
263 
264     private void handleLifecycleException(FacesContext facesContext, Exception e) throws IOException, ServletException {
265 
266         boolean errorHandling = getBooleanValue(facesContext.getExternalContext().getInitParameter(ERROR_HANDLING_PARAMETER), true);
267 
268         if(errorHandling) {
269             String errorHandlerClass = facesContext.getExternalContext().getInitParameter(ERROR_HANDLER_PARAMETER);
270             if(errorHandlerClass != null) {
271                 try {
272                     Class clazz = Class.forName(errorHandlerClass);
273 
274                     Object errorHandler = clazz.newInstance();
275 
276                     Method m = clazz.getMethod("handleException", new Class[]{FacesContext.class,Exception.class});
277                     m.invoke(errorHandler, new Object[]{facesContext, e});
278                 }
279                 catch(ClassNotFoundException ex) {
280                     throw new ServletException("Error-Handler : " +errorHandlerClass+ " was not found. Fix your web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
281                 } catch (IllegalAccessException ex) {
282                     throw new ServletException("Constructor of error-Handler : " +errorHandlerClass+ " is not accessible. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
283                 } catch (InstantiationException ex) {
284                     throw new ServletException("Error-Handler : " +errorHandlerClass+ " could not be instantiated. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
285                 } catch (NoSuchMethodException ex) {
286                     log.error("Error-Handler : " +errorHandlerClass+ " did not have a method with name : handleException and parameters : javax.faces.context.FacesContext, java.lang.Exception. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
287                     //Try to look if it is implemented more general method handleThrowable
288                     handleLifecycleThrowable(facesContext, e);
289                 } catch (InvocationTargetException ex) {
290                     throw new ServletException("Excecution of method handleException in Error-Handler : " +errorHandlerClass+ " caused an exception. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
291                 }
292             }
293             else {
294                 _ErrorPageWriter.handleException(facesContext, e);
295             }
296         }
297         else {
298             _ErrorPageWriter.throwException(e);
299         }
300     }
301     
302     private void handleLifecycleThrowable(FacesContext facesContext, Throwable e) throws IOException, ServletException {
303 
304         boolean errorHandling = getBooleanValue(facesContext.getExternalContext().getInitParameter(ERROR_HANDLING_PARAMETER), true);
305 
306         if(errorHandling) {
307             String errorHandlerClass = facesContext.getExternalContext().getInitParameter(ERROR_HANDLER_PARAMETER);            
308             if(errorHandlerClass != null) {
309                 try {
310                     Class clazz = Class.forName(errorHandlerClass);
311 
312                     Object errorHandler = clazz.newInstance();
313 
314                     Method m = clazz.getMethod("handleThrowable", new Class[]{FacesContext.class,Throwable.class});
315                     m.invoke(errorHandler, new Object[]{facesContext, e});
316                 }
317                 catch(ClassNotFoundException ex) {
318                     throw new ServletException("Error-Handler : " +errorHandlerClass+ " was not found. Fix your web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
319                 } catch (IllegalAccessException ex) {
320                     throw new ServletException("Constructor of error-Handler : " +errorHandlerClass+ " is not accessible. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
321                 } catch (InstantiationException ex) {
322                     throw new ServletException("Error-Handler : " +errorHandlerClass+ " could not be instantiated. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
323                 } catch (NoSuchMethodException ex) {
324                     throw new ServletException("Error-Handler : " +errorHandlerClass+ " did not have a method with name : handleException and parameters : javax.faces.context.FacesContext, java.lang.Exception. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
325                 } catch (InvocationTargetException ex) {
326                     throw new ServletException("Excecution of method handleException in Error-Handler : " +errorHandlerClass+ " threw an exception. Error-Handler is specified in web.xml-parameter : "+ERROR_HANDLER_PARAMETER,ex);
327                 }
328             }
329             else {
330                 _ErrorPageWriter.handleThrowable(facesContext, e);
331             }
332         }
333         else {
334             _ErrorPageWriter.throwException(e);
335         }
336     }
337 
338     private static boolean getBooleanValue(String initParameter, boolean defaultVal) {
339 
340         if(initParameter == null || initParameter.trim().length()==0)
341             return defaultVal;
342 
343         return (initParameter.equalsIgnoreCase("on") || initParameter.equals("1") || initParameter.equalsIgnoreCase("true"));
344     }
345 
346     private FacesContext prepareFacesContext(ServletRequest request, ServletResponse response) {
347         FacesContext facesContext
348                 = _facesContextFactory.getFacesContext(_servletConfig.getServletContext(),
349                                                        request,
350                                                        response,
351                                                        _lifecycle);
352         return facesContext;
353     }
354 }