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.webapp.filter;
20  
21  import java.io.IOException;
22  
23  import javax.servlet.Filter;
24  import javax.servlet.FilterChain;
25  import javax.servlet.FilterConfig;
26  import javax.servlet.ServletContext;
27  import javax.servlet.ServletException;
28  import javax.servlet.ServletRequest;
29  import javax.servlet.ServletResponse;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.apache.commons.fileupload.servlet.ServletFileUpload;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.myfaces.renderkit.html.util.AddResource;
37  import org.apache.myfaces.renderkit.html.util.AddResource2;
38  import org.apache.myfaces.renderkit.html.util.AddResourceFactory;
39  
40  /**
41   * This filter provides a number of functions that many tomahawk components require.
42   * <p>
43   * In tomahawk versions up to and including 1.1.6, it is mandatory to define this filter in the application's
44   * web.xml in order to use some tomahawk components. In Tomahawk version 1.1.7, this filter is now optional;
45   * when defined it will be used as for earlier versions. When omitted, the same functionality is now
46   * automatically provided via classes TomahawkFacesContextFactory and ServeResourcePhaseListener. 
47   *
48   * <h2>Response Buffering</h2>
49   * 
50   * When the request is for a JSF page, and the configured "resource manager"
51   * requires response buffering then a response wrapper is created which
52   * buffers the entire output from the current request in memory.
53   * <p>
54   * Tomahawk provides at least three "resource managers":
55   * <ul>
56   * <li> DefaultAddResources (the default) does require response buffering.
57   * <li> NonBufferingAddResource does not require response buffering, but it
58   * renders javascript on body and offer support in portlets enviroments. 
59   * <li> StreamingAddResources does not, but only provides a subset of the
60   * features of DefaultAddResources and therefore does not work with all
61   * Tomahawk components.
62   * </ul>
63   * <p>
64   * Only one "resource manager" may be configured for a webapp. See class
65   * AddResourceFactory for further details on configuring this.
66   * <p>
67   * When DefaultAddResources is enabled (default behaviour), the resulting
68   * response buffering does cause some unnecessary memory and performance
69   * impact for pages where no component in the page actually registers a
70   * resource that needs to be inserted into the page - but whether a page
71   * does that or not cannot be known until after the page has been rendered.
72   * In the rare case where a request to a JSF page generates non-html
73   * output (eg a PDF is generated as a response to a submit of a jsf page),
74   * the data is unfortunately buffered. However it is not post-processed,
75   * because its http content-type header will not be "text/html" (see other
76   * documentation for this class).
77   *
78   * <h2>Inserting Resources into HTML responses (DefaultAddResources)</h2>
79   * 
80   * After the response has been completely generated (and cached in memory),
81   * this filter checks the http content-type header. If it is not html or xhtml,
82   * then the data is simply send to the response stream without further processing.
83   * 
84   * For html or xhtml responses, this filter causes the data to be post-processed
85   * to insert any "resources" registered via the AddResources framework. This
86   * allows jsf components (and other code if it wants) to register data that
87   * should be output into an HTML page, particularly into places like an html
88   * "head" section, even when the component occurs after that part of the
89   * response has been generated.
90   * <p>
91   * The default "resource manager" (DefaultAddResources) supports inserting
92   * resources into any part of the generated page. The alternate class
93   * StreamingAddResources does not; it does not buffer output and therefore
94   * can only insert resources for a jsf component  into the page after the
95   * point at which that component is rendered. In particular, this means that
96   * components that use external javascript files will not work with that
97   * "resource manager" as [script href=...] only works in the head section
98   * of an html page.
99   *
100  * <h2>Serving Resources from the Classpath</h2>
101  * 
102  * Exactly what text gets inserted into an HTML page via a "resource"
103  * (see above) is managed by the AddResources class, not this one. However
104  * often it is useful for jsf components to be able to refer to resources
105  * that are on the classpath, and in particular when the resource is in the
106  * same jar as the component code. Servlet engines do not support serving
107  * resources from the classpath. However the AddResources framework can be
108  * used to generate a special url prefix that this filter can be mapped to,
109  * allowing this to be done. In particular, many Tomahawk components use
110  * this to bundle their necessary resources within the tomahawk jarfile.
111  * <p>
112  * When a request to such a url is found by this filter, the actual resource
113  * is located and streamed back to the user (no buffering required). See the
114  * AddResource class documentation for further details.
115  *
116  * <h2>Handling File Uploads</h2>
117  * 
118  * Sometimes an application needs to allow a user to send a file of data
119  * to the web application. The html page needs only to include an input
120  * tag with type=file; clicking on this will prompt the user for a file
121  * to send. The browser will then send an http request to the server
122  * which is marked as a "mime multipart request" with normal http post
123  * parameters in one mime part, and the file contents in another mime part.
124  * <p>
125  * This filter also handles such requests, using the Apache HttpClient
126  * library to save the file into a configurable local directory before
127  * allowing the normal processing for the url that the post request
128  * refers to. A number of configuration properties on this filter control
129  * maximum file upload sizes and various other useful settings. See the
130  * documentation for the init method for more details.
131  * 
132  * <h2>Avoiding Processing</h2>
133  * 
134  * When the ExtensionsFilter is enabled, and the DefaultAddResources
135  * implementation is used then there is no way to avoid having the
136  * response buffered in memory. However as long as the mime-type set
137  * for the response data is <i>not</i> text/html then the data will
138  * be written out without any modifications.
139  * 
140  * @author Sylvain Vieujot (latest modification by $Author: lu4242 $)
141  * @version $Revision: 954965 $ $Date: 2010-06-15 11:58:31 -0500 (Mar, 15 Jun 2010) $
142  */
143 public class ExtensionsFilter implements Filter {
144 
145     private Log log = LogFactory.getLog(ExtensionsFilter.class);
146     
147     private int _uploadMaxSize = 100 * 1024 * 1024; // 100 MB
148 
149     private int _uploadMaxFileSize = 100 * 1024 * 1024; // 100 MB
150 
151     private int _uploadThresholdSize = 1 * 1024 * 1024; // 1 MB
152 
153     private String _uploadRepositoryPath = null; //standard temp directory
154     
155     private boolean _cacheFileSizeErrors = false; 
156 
157     private ServletContext _servletContext;
158 
159     public static final String DOFILTER_CALLED = "org.apache.myfaces.component.html.util.ExtensionFilter.doFilterCalled";
160 
161     /**
162      * Flag that indicates extensions filter initialization code has been done.
163      * This flag is added on application map.
164      */
165     public static final String EXTENSIONS_FILTER_INITIALIZED = "org.apache.myfaces.tomahawk.EXTENSIONS_FILTER_INITIALIZED";
166 
167     /**
168      * Init method for this filter.
169      * <p>
170      * The following filter init parameters can be configured in the web.xml file
171      * for this filter:
172      * <ul>
173      * <li>uploadMaxFileSize</li>
174      * <li>uploadThresholdSize</li>
175      * <li>uploadRepositoryPath</li>
176      * <li>uploadMaxSize</li>
177      * <li>cacheFileSizeErrors</li>
178      * </ul>
179      * </p>
180      * <p>
181      * All size parameters may have the suffix "g" (gigabytes), "m" (megabytes) or "k" (kilobytes).
182      * </p>
183      * 
184      * <h2>uploadMaxFileSize</h2>
185      * 
186      * Sets the maximum allowable size for uploaded files.
187      * <p>
188      * If the user attempts to upload a file which is larger than this, then the data <i>is</i>
189      * transmitted from the browser to the server (this cannot be prevented with standard HTML
190      * functionality). However the file will not be saved in memory or on disk. An error message
191      * will be added to the standard JSF error messages, and the page re-rendered (as for a
192      * validation failure).
193      * </p>
194      * <p>
195      * The default value is 100 Megabytes.
196      * </p>
197      * 
198      * <h2>uploadThresholdSize</h2>
199      * 
200      * Sets the size threshold beyond which files are written directly to disk. Files which are
201      * smaller than this are simply held in memory. The default is 1 Megabyte.
202      * 
203      * <h2>uploadRepositoryPath</h2>
204      * 
205      * Sets the directory in which temporary files (ie caches for those uploaded files that
206      * are larger than uploadThresholdSize) are to be stored.
207      * 
208      * <h2>uploadMaxSize</h2>
209      * 
210      * Sets the maximum allowable size for the current request. If not set, its value is the 
211      * value set on uploadMaxFileSize param. 
212      * 
213      * <h2>cacheFileSizeErrors</h2>
214      * 
215      * Catch and swallow FileSizeLimitExceededExceptions in order to return as
216      * many usable items as possible.
217      * 
218      */
219     public void init(FilterConfig filterConfig) {
220         // Note that the code here to extract FileUpload configuration params is not actually used.
221         // The handling of multipart requests was moved from this Filter into a custom FacesContext
222         // (TomahawkFacesContextWrapper) so that Portlets could be supported (Portlets cannot use
223         // servlet filters).
224         //
225         // For backwards compatibility, the TomahawkFacesContextWrapper class *parses* the
226         // web.xml to retrieve these same filter config params. That is IMO seriously ugly
227         // and hopefully will be fixed.
228 
229         String param = filterConfig.getInitParameter("uploadMaxFileSize");
230 
231         _uploadMaxFileSize = resolveSize(param, _uploadMaxFileSize);
232 
233         param = filterConfig.getInitParameter("uploadMaxSize");
234 
235         if (param != null)
236         {
237             _uploadMaxSize = resolveSize(param, _uploadMaxSize);
238         }
239         else
240         {
241             //If not set, default to uploadMaxFileSize
242             _uploadMaxSize = resolveSize(param, _uploadMaxFileSize);
243         }
244         
245         param = filterConfig.getInitParameter("uploadThresholdSize");
246 
247         _uploadThresholdSize = resolveSize(param, _uploadThresholdSize);
248 
249         _uploadRepositoryPath = filterConfig.getInitParameter("uploadRepositoryPath");
250         
251         _cacheFileSizeErrors = getBooleanValue(filterConfig.getInitParameter("cacheFileSizeErrors"), false);
252 
253         _servletContext = filterConfig.getServletContext();
254         
255         filterConfig.getServletContext().setAttribute(EXTENSIONS_FILTER_INITIALIZED, true);
256     }
257 
258     private int resolveSize(String param, int defaultValue) {
259         int numberParam = defaultValue;
260 
261         if (param != null) {
262             param = param.toLowerCase();
263             int factor = 1;
264             String number = param;
265 
266             if (param.endsWith("g")) {
267                 factor = 1024 * 1024 * 1024;
268                 number = param.substring(0, param.length() - 1);
269             } else if (param.endsWith("m")) {
270                 factor = 1024 * 1024;
271                 number = param.substring(0, param.length() - 1);
272             } else if (param.endsWith("k")) {
273                 factor = 1024;
274                 number = param.substring(0, param.length() - 1);
275             }
276 
277             numberParam = Integer.parseInt(number) * factor;
278         }
279         return numberParam;
280     }
281     
282     private static boolean getBooleanValue(String initParameter, boolean defaultVal)
283     {
284         if(initParameter == null || initParameter.trim().length()==0)
285             return defaultVal;
286 
287         return (initParameter.equalsIgnoreCase("on") || initParameter.equals("1") || initParameter.equalsIgnoreCase("true"));
288     }
289 
290     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
291 
292         if(request.getAttribute(DOFILTER_CALLED)!=null)
293         {
294             chain.doFilter(request, response);
295             return;
296         }
297 
298         request.setAttribute(DOFILTER_CALLED,"true");
299 
300         if (!(response instanceof HttpServletResponse)) {
301             //If this is a portlet request, just continue the chaining
302             chain.doFilter(request, response);
303             return;
304         }
305 
306         HttpServletResponse httpResponse = (HttpServletResponse) response;
307         HttpServletRequest httpRequest = (HttpServletRequest) request;
308 
309         // Serve resources
310         AddResource addResource;
311 
312         try
313         {
314             addResource = AddResourceFactory.getInstance(httpRequest,_servletContext);
315             if( addResource.isResourceUri(_servletContext, httpRequest ) ){
316                 addResource.serveResource(_servletContext, httpRequest, httpResponse);
317                 return;
318             }
319         }
320         catch(Throwable th)
321         {
322             log.error("Exception wile retrieving addResource",th);
323             throw new ServletException(th);
324         }
325 
326         HttpServletRequest extendedRequest = httpRequest;
327 
328         // For multipart/form-data requests
329         // This is done by TomahawkFacesContextWrapper
330         if (ServletFileUpload.isMultipartContent(httpRequest)) {
331             extendedRequest = new MultipartRequestWrapper(httpRequest, _uploadMaxFileSize, 
332                     _uploadThresholdSize, _uploadRepositoryPath, _uploadMaxSize, _cacheFileSizeErrors);
333         }
334         
335         try
336         {
337             if (addResource instanceof AddResource2)
338             {
339                 ((AddResource2)addResource).responseStarted(_servletContext, extendedRequest);
340             }
341             else
342             {
343                 addResource.responseStarted();
344             }
345             
346             //This case is necessary when is used            
347             //org.apache.myfaces.renderkit.html.util.DefaultAddResource
348             //Buffers the output and add to the header the necessary stuff
349             //In other case this is simply ignored (NonBufferingAddResource and
350             //StreamingAddResource), because this not require buffering
351             //and the chaining continues.
352             if (addResource.requiresBuffer())
353             {
354                 ExtensionsResponseWrapper extendedResponse = new ExtensionsResponseWrapper((HttpServletResponse) response);
355         
356                 // Standard request
357                 chain.doFilter(extendedRequest, extendedResponse);
358         
359                 extendedResponse.finishResponse();
360         
361                 // write the javascript stuff for myfaces and headerInfo, if needed
362                 HttpServletResponse servletResponse = (HttpServletResponse)response;
363         
364                 // only parse HTML responses
365                 if (extendedResponse.getContentType() != null && isValidContentType(extendedResponse.getContentType()))
366                 {
367                     addResource.parseResponse(extendedRequest, extendedResponse.toString(),
368                             servletResponse);
369         
370                     addResource.writeMyFacesJavascriptBeforeBodyEnd(extendedRequest,
371                             servletResponse);
372         
373                     if( ! addResource.hasHeaderBeginInfos() ){
374                         // writes the response if no header info is needed
375                         addResource.writeResponse(extendedRequest, servletResponse);
376                         return;
377                     }
378         
379                     // Some headerInfo has to be added
380                     addResource.writeWithFullHeader(extendedRequest, servletResponse);
381         
382                     // writes the response
383                     addResource.writeResponse(extendedRequest, servletResponse);
384                 }
385                 else
386                 {
387 
388                     byte[] responseArray = extendedResponse.getBytes();
389 
390                     if(responseArray.length > 0)
391                     {
392                         // When not filtering due to not valid content-type, deliver the byte-array instead of a charset-converted string.
393                         // Otherwise a binary stream gets corrupted.
394                         servletResponse.getOutputStream().write(responseArray);
395                     }
396                 }
397             }
398             else
399             {
400                 chain.doFilter(extendedRequest, response);
401             }
402         }
403         finally
404         {
405             addResource.responseFinished();         
406         }
407         
408         //chain.doFilter(extendedRequest, response);
409     }
410 
411     public boolean isValidContentType(String contentType)
412     {
413         return contentType.startsWith("text/html") ||
414                 contentType.startsWith("text/xml") ||
415                 contentType.startsWith("application/xhtml+xml") ||
416                 contentType.startsWith("application/xml");
417     }
418 
419     /**
420      * Destroy method for this filter
421      */
422     public void destroy() {
423         // NoOp
424     }
425 
426 
427 }