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