001    /*
002     * Copyright © 2008, 2009 Pedro Agulló Soliveres.
003     * 
004     * This file is part of DirectJNgine.
005     *
006     * DirectJNgine is free software: you can redistribute it and/or modify
007     * it under the terms of the GNU General Public License as published by
008     * the Free Software Foundation, either version 3 of the License.
009     *
010     * Commercial use is permitted to the extent that the code/component(s)
011     * do NOT become part of another Open Source or Commercially developed
012     * licensed development library or toolkit without explicit permission.
013     *
014     * DirectJNgine is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017     * GNU General Public License for more details.
018     *
019     * You should have received a copy of the GNU General Public License
020     * along with DirectJNgine.  If not, see <http://www.gnu.org/licenses/>.
021     * 
022     * This software uses the ExtJs library (http://extjs.com), which is 
023     * distributed under the GPL v3 license (see http://extjs.com/license).
024     */
025    
026    package com.softwarementors.extjs.djn.servlet;
027    
028    import java.io.IOException;
029    import java.util.ArrayList;
030    import java.util.List;
031    
032    import javax.servlet.ServletConfig;
033    import javax.servlet.ServletException;
034    import javax.servlet.http.HttpServlet;
035    import javax.servlet.http.HttpServletRequest;
036    import javax.servlet.http.HttpServletResponse;
037    
038    import org.apache.commons.fileupload.FileItem;
039    import org.apache.commons.fileupload.FileUploadException;
040    import org.apache.commons.fileupload.servlet.ServletFileUpload;
041    import org.apache.log4j.Logger;
042    import org.apache.log4j.NDC;
043    
044    import com.softwarementors.extjs.djn.EncodingUtils;
045    import com.softwarementors.extjs.djn.ServletUtils;
046    import com.softwarementors.extjs.djn.StringUtils;
047    import com.softwarementors.extjs.djn.Timer;
048    import com.softwarementors.extjs.djn.api.Registry;
049    import com.softwarementors.extjs.djn.config.ApiConfiguration;
050    import com.softwarementors.extjs.djn.config.GlobalConfiguration;
051    import com.softwarementors.extjs.djn.gson.GsonBuilderConfigurator;
052    import com.softwarementors.extjs.djn.jscodegen.CodeFileGenerator;
053    import com.softwarementors.extjs.djn.router.RequestRouter;
054    import com.softwarementors.extjs.djn.router.RequestType;
055    import com.softwarementors.extjs.djn.router.processor.RequestException;
056    import com.softwarementors.extjs.djn.router.processor.poll.PollRequestProcessor;
057    import com.softwarementors.extjs.djn.router.processor.standard.form.upload.UploadFormPostRequestProcessor;
058    import com.softwarementors.extjs.djn.scanner.Scanner;
059    
060    public class DirectJNgineServlet extends HttpServlet {
061    
062      private static final Logger logger = Logger.getLogger( DirectJNgineServlet.class);
063    
064      /*********************************************************  
065       * GlobalParameters and configuration
066       *********************************************************/
067      private static final String VALUES_SEPARATOR = ",";
068    
069      public static class GlobalParameters {
070        public static final String PROVIDERS_URL = "providersUrl";
071        public static final String DEBUG = "debug";
072        public static final String GSON_BUILDER_CONFIGURATOR_CLASS = "gsonBuilderConfiguratorClass";
073        public static final String REGISTRY_CONFIGURATOR_CLASS = "registryConfiguratorClass";
074        
075        private static final String APIS_PARAMETER = "apis";
076        private static final String MINIFY = "minify";
077    
078        public static final String BATCH_REQUESTS_MULTITHREADING_ENABLED = "batchRequestsMultithreadingEnabled";
079        public static final String BATCH_REQUESTS_MIN_THREADS_POOOL_SIZE = "batchRequestsMinThreadsPoolSize";
080        public static final String BATCH_REQUESTS_MAX_THREADS_POOOL_SIZE = "batchRequestsMaxThreadsPoolSize";
081        public static final String BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS = "batchRequestsMaxThreadKeepAliveSeconds";
082        public static final String BATCH_REQUESTS_MAX_THREADS_PER_REQUEST = "batchRequestsMaxThreadsPerRequest";
083      }
084    
085      public static class ApiParameters {
086        public static final String API_FILE = "apiFile";
087        public static final String API_NAMESPACE = "apiNamespace";
088        public static final String ACTIONS_NAMESPACE = "actionsNamespace";
089        public static final String CLASSES = "classes";
090      }
091    
092      private RequestRouter processor;
093      private ServletFileUpload uploader;
094      private long id = 1000; // It is good we will get lots of ids with the same number of digits...
095      private synchronized long getUniqueRequestId() {
096        return this.id++;
097      }
098      
099      @Override
100      public void init(ServletConfig configuration) throws ServletException
101      {
102        assert configuration != null;
103        super.init(configuration);
104        
105        createDirectJNgineRouter(configuration);
106      }
107    
108      protected void createDirectJNgineRouter(ServletConfig configuration) throws ServletException {
109        assert configuration != null;
110        
111        Timer timer = new Timer();
112    
113        Timer subtaskTimer = new Timer();
114        GlobalConfiguration globalConfiguration = createGlobalConfiguration(configuration);    
115        List<ApiConfiguration> apiConfigurations = createApiConfigurationsFromServletConfigurationApi(configuration);
116        subtaskTimer.stop();
117        subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Servlet Configuration Load time");
118        
119        subtaskTimer.restart();
120        Registry registry = new Registry( globalConfiguration );
121        Scanner scanner = new Scanner();
122        scanner.scanAndRegisterApiConfigurations( registry, apiConfigurations );
123        subtaskTimer.stop();
124        subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Standard Api processing time");
125    
126        if( globalConfiguration.hasCustomRegistryConfigurationClass() ) {
127          subtaskTimer.restart();
128          performCustomRegistryConfiguration( configuration, registry );
129          subtaskTimer.stop();
130          subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Custom Registry processing time");
131        }
132        
133        subtaskTimer.restart();
134        try {
135          CodeFileGenerator.updateApiFiles(registry);
136          subtaskTimer.stop();
137          subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Api Files creation time");
138        }
139        catch( IOException ex ) {
140          ServletException e = new ServletException( "Unable to create DirectJNgine API files",  ex );
141          logger.fatal( e.getMessage(), e );
142           throw e;
143        }
144        
145        subtaskTimer.restart();
146        this.uploader = UploadFormPostRequestProcessor.createFileUploader();
147        this.processor = new RequestRouter( registry, globalConfiguration );
148        subtaskTimer.stop();
149        subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Request Processor initialization time");
150        
151        timer.stop();
152        timer.logDebugTimeInMilliseconds("Djn initialization: total DirectJNgine initialization time");
153      }
154      
155      private void performCustomRegistryConfiguration(ServletConfig configuration, Registry registry) {
156        assert configuration != null;
157        assert registry != null;
158        assert registry.getGlobalConfiguration().hasCustomRegistryConfigurationClass();
159        
160        createCustomRegistryConfigurator( registry.getGlobalConfiguration().getRegistryConfiguratorClass() ).configure( registry, configuration);
161      }
162      
163      private RegistryConfigurator createCustomRegistryConfigurator( Class<? extends RegistryConfigurator> configuratorClass ) {
164        assert configuratorClass != null;
165        
166        try {
167          return configuratorClass.newInstance();
168        }
169        catch (InstantiationException e) {
170          RegistryConfiguratorException ex = RegistryConfiguratorException.forUnableToInstantiateRegistryConfigurator(configuratorClass, e);
171          logger.fatal( ex.getMessage(), ex);
172          throw ex;
173        }
174        catch (IllegalAccessException e) {
175          RegistryConfiguratorException ex = RegistryConfiguratorException.forUnableToInstantiateRegistryConfigurator(configuratorClass, e);
176          logger.fatal( ex.getMessage(), ex);
177          throw ex;
178        }     
179      }
180      
181      
182    
183      protected GlobalConfiguration createGlobalConfiguration(ServletConfig configuration) {
184        assert configuration != null;
185        
186        ServletUtils.checkRequiredParameters(configuration, GlobalParameters.PROVIDERS_URL);
187        
188        boolean isDebug = ServletUtils.getBooleanParameter( configuration, GlobalParameters.DEBUG, GlobalConfiguration.DEFAULT_DEBUG_VALUE);
189        String providersUrl = ServletUtils.getRequiredParameter(configuration, GlobalParameters.PROVIDERS_URL);
190        String gsonConfiguratorClassName = ServletUtils.getParameter(configuration, GlobalParameters.GSON_BUILDER_CONFIGURATOR_CLASS, GlobalConfiguration.DEFAULT_GSON_BUILDER_CONFIGURATOR_CLASS.getName());
191        Class<? extends GsonBuilderConfigurator> gsonConfiguratorClass = getGsonBuilderConfiguratorClass(gsonConfiguratorClassName);
192        String registryConfiguratorClassName = ServletUtils.getParameter(configuration, GlobalParameters.REGISTRY_CONFIGURATOR_CLASS, null);
193        Class<? extends RegistryConfigurator> registryConfiguratorClass = getRegistryConfiguratorClass(registryConfiguratorClassName);
194        
195        // Global multithreaded-batched requests support parameters
196        boolean isBatchRequestsMultithreadingEnabled = ServletUtils.getBooleanParameter( configuration, GlobalParameters.BATCH_REQUESTS_MULTITHREADING_ENABLED, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MULTITHREADING_ENABLED_VALUE);
197        boolean minifyEnabled = ServletUtils.getBooleanParameter( configuration, GlobalParameters.MINIFY, GlobalConfiguration.DEFAULT_MINIFY_VALUE);
198        
199        int batchRequestsMinThreadsPoolSize = ServletUtils.getIntParameterGreaterOrEqualToValue(
200           configuration, GlobalParameters.BATCH_REQUESTS_MIN_THREADS_POOOL_SIZE, 
201           GlobalConfiguration.MIN_BATCH_REQUESTS_MIN_THREAD_POOL_SIZE, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MIN_THREAD_POOL_SIZE);
202        int batchRequestsMaxThreadsPoolSize = ServletUtils.getIntParameterGreaterOrEqualToValue(
203            configuration, GlobalParameters.BATCH_REQUESTS_MAX_THREADS_POOOL_SIZE, 
204            GlobalConfiguration.MIN_BATCH_REQUESTS_MAX_THREAD_POOL_SIZE, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MAX_THREAD_POOL_SIZE);
205        int batchRequestsThreadKeepAliveSeconds = ServletUtils.getIntParameterGreaterOrEqualToValue(
206            configuration, GlobalParameters.BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS, 
207            GlobalConfiguration.MIN_BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS);
208        int batchRequestsMaxThreadsPerRequest = ServletUtils.getIntParameterGreaterOrEqualToValue(
209            configuration, GlobalParameters.BATCH_REQUESTS_MAX_THREADS_PER_REQUEST, 
210            GlobalConfiguration.MIN_BATCH_REQUESTS_MAX_THREADS_PER_REQUEST, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MAX_THREADS_PER_REQUEST);
211        
212        if( batchRequestsMinThreadsPoolSize > batchRequestsMaxThreadsPoolSize ) {
213          ServletConfigurationException ex = ServletConfigurationException.forMaxThreadPoolSizeMustBeEqualOrGreaterThanMinThreadPoolSize(batchRequestsMinThreadsPoolSize, batchRequestsMaxThreadsPoolSize);
214          logger.fatal( ex.getMessage(), ex );
215          throw ex;
216        }
217        
218        if( logger.isInfoEnabled() ) {
219          logger.info( "Servlet GLOBAL configuration: " + 
220            GlobalParameters.DEBUG + "=" + isDebug + ", " +
221            GlobalParameters.PROVIDERS_URL + "=" + providersUrl + ", " +
222            GlobalParameters.GSON_BUILDER_CONFIGURATOR_CLASS + "=" + gsonConfiguratorClassName + ", " +
223            GlobalParameters.REGISTRY_CONFIGURATOR_CLASS + "=" + registryConfiguratorClassName + ", " +
224            GlobalParameters.MINIFY + "=" + minifyEnabled + ", " +
225            GlobalParameters.BATCH_REQUESTS_MULTITHREADING_ENABLED + "=" + isBatchRequestsMultithreadingEnabled + ", " + 
226            GlobalParameters.BATCH_REQUESTS_MIN_THREADS_POOOL_SIZE + "=" + batchRequestsMinThreadsPoolSize + ", " + 
227            GlobalParameters.BATCH_REQUESTS_MAX_THREADS_POOOL_SIZE + "=" + batchRequestsMaxThreadsPoolSize + ", " + 
228            GlobalParameters.BATCH_REQUESTS_MAX_THREADS_PER_REQUEST + "=" + batchRequestsMaxThreadsPerRequest + ", " + 
229            GlobalParameters.BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS + "=" + batchRequestsThreadKeepAliveSeconds 
230          );
231        }
232        
233        GlobalConfiguration result = new GlobalConfiguration( providersUrl, isDebug, 
234          gsonConfiguratorClass, registryConfiguratorClass, 
235          minifyEnabled, 
236          isBatchRequestsMultithreadingEnabled, batchRequestsMinThreadsPoolSize, batchRequestsMaxThreadsPoolSize,
237          batchRequestsThreadKeepAliveSeconds, batchRequestsMaxThreadsPerRequest );
238        return result;
239      }
240    
241      private Class<? extends GsonBuilderConfigurator> getGsonBuilderConfiguratorClass(String gsonConfiguratorClassName) {
242        assert !StringUtils.isEmpty(gsonConfiguratorClassName);
243        
244        Class<? extends GsonBuilderConfigurator> configuratorClass;
245        try {
246          configuratorClass = loadGsonConfiguratorClass(gsonConfiguratorClassName);
247          if( !GsonBuilderConfigurator.class.isAssignableFrom(configuratorClass)) {
248            ServletConfigurationException ex = ServletConfigurationException.forGsonBuilderConfiguratorMustImplementGsonBuilderConfiguratorInterface(gsonConfiguratorClassName ); 
249            logger.fatal( ex.getMessage(), ex );
250            throw ex;
251          }
252          return configuratorClass;
253        }
254        catch( ClassNotFoundException ex ) {
255          ServletConfigurationException e = ServletConfigurationException.forClassNotFound(gsonConfiguratorClassName, ex ); 
256          logger.fatal( e.getMessage(), e );
257          throw e;
258        }
259      }
260    
261      private Class<? extends RegistryConfigurator> getRegistryConfiguratorClass(String registryConfiguratorClassName) {
262        if( StringUtils.isEmpty(registryConfiguratorClassName)) {
263          return null;
264        }
265        
266        Class<? extends RegistryConfigurator> configuratorClass;
267        try {
268          configuratorClass = loadRegistryConfiguratorClass(registryConfiguratorClassName);
269          if( !RegistryConfigurator.class.isAssignableFrom(configuratorClass)) {
270            ServletConfigurationException ex = ServletConfigurationException.forRegistryConfiguratorMustImplementGsonBuilderConfiguratorInterface(registryConfiguratorClassName ); 
271            logger.fatal( ex.getMessage(), ex );
272            throw ex;
273          }
274          return configuratorClass;
275        }
276        catch( ClassNotFoundException ex ) {
277          ServletConfigurationException e = ServletConfigurationException.forClassNotFound(registryConfiguratorClassName, ex ); 
278          logger.fatal( e.getMessage(), e );
279          throw e;
280        }
281      }
282    
283      @SuppressWarnings("unchecked") // Avoid generics typecast warning
284      private Class<GsonBuilderConfigurator> loadGsonConfiguratorClass(String gsonConfiguratorClassName)
285          throws ClassNotFoundException {
286        assert !StringUtils.isEmpty(gsonConfiguratorClassName);
287        
288        return (Class<GsonBuilderConfigurator>)Class.forName(gsonConfiguratorClassName);
289      }
290    
291      @SuppressWarnings("unchecked") // Avoid generics typecast warning
292      private Class<RegistryConfigurator> loadRegistryConfiguratorClass(String registryConfiguratorClassName)
293          throws ClassNotFoundException {
294        assert !StringUtils.isEmpty(registryConfiguratorClassName);
295        
296        return (Class<RegistryConfigurator>)Class.forName(registryConfiguratorClassName);
297      }
298    
299      protected List<ApiConfiguration> createApiConfigurationsFromServletConfigurationApi(ServletConfig configuration) {
300        assert configuration != null;
301    
302        List<ApiConfiguration> result = new ArrayList<ApiConfiguration>();    
303        String apisParameter = ServletUtils.getRequiredParameter(configuration, GlobalParameters.APIS_PARAMETER);
304        List<String> apis = StringUtils.getNonBlankValues(apisParameter, VALUES_SEPARATOR);
305        logger.info( "Servlet APIs configuration: " + GlobalParameters.APIS_PARAMETER + "=" + apisParameter ); 
306        
307        for( String api : apis) {
308          ApiConfiguration apiConfiguration = createApiConfigurationFromServletConfigurationApi( configuration, api );
309          result.add( apiConfiguration );
310        }
311        
312        if( result.isEmpty() ) {
313          logger.warn( "No apis specified");
314        }
315        
316        return result;
317      }
318    
319      private ApiConfiguration createApiConfigurationFromServletConfigurationApi(ServletConfig configuration, String api) {
320        assert configuration != null;
321        assert !StringUtils.isEmpty(api);
322        
323        String apiFile = ServletUtils.getParameter( configuration, api + "." + ApiParameters.API_FILE, api + ApiConfiguration.DEFAULT_API_FILE_SUFFIX );
324        String fullGeneratedApiFile = getServletContext().getRealPath(apiFile);    
325        String apiNamespace = ServletUtils.getParameter( configuration, api + "." + ApiParameters.API_NAMESPACE, "" );
326        String actionsNamespace = ServletUtils.getParameter( configuration, api + "." + ApiParameters.ACTIONS_NAMESPACE, "" );
327    
328        // If apiNamespace is empty, try to use actionsNamespace: if still empty, use the api name itself
329        if( apiNamespace.equals("")) {
330          if( actionsNamespace.equals("")) {
331            apiNamespace = ApiConfiguration.DEFAULT_NAMESPACE_PREFIX + api;
332            if( logger.isDebugEnabled() ) {
333              logger.debug( "Using the api name, prefixed with '" + ApiConfiguration.DEFAULT_NAMESPACE_PREFIX + "' as the value for " + ApiParameters.API_NAMESPACE);          
334            }
335          }
336          else {
337            apiNamespace = actionsNamespace;
338            logger.debug( "Using " + ApiParameters.ACTIONS_NAMESPACE + " as the value for " + ApiParameters.API_NAMESPACE);                  
339          }
340        }
341        
342        String classNames = ServletUtils.getParameter( configuration, api + "." + ApiParameters.CLASSES, "" );
343        List<Class<?>> classes = getClasses( classNames );
344        
345        if( logger.isInfoEnabled() ) {
346          logger.info( "Servlet '" + api + "' Api configuration: " +
347            ApiParameters.API_NAMESPACE + "=" + apiNamespace + ", " +
348            ApiParameters.ACTIONS_NAMESPACE + "=" + actionsNamespace + ", " +
349            ApiParameters.API_FILE + "=" + apiFile + " => Api file: " + fullGeneratedApiFile + ", " +
350            ApiParameters.CLASSES + "=" + classNames);
351        }
352        
353        if( classes.isEmpty() ) {
354          logger.warn( "There are no action classes to register for api '" + api + "'");
355        }
356        ApiConfiguration apiConfiguration = new ApiConfiguration( api, fullGeneratedApiFile, apiNamespace, actionsNamespace, classes );
357            
358        return apiConfiguration;
359      }
360    
361      private static List<Class<?>> getClasses( String classes )  {
362        assert classes != null;
363        
364        List<Class<?>> result = new ArrayList<Class<?>>();
365        if( StringUtils.isEmpty(classes) ) {
366          return result;
367        }
368        List<String> classNames = StringUtils.getNonBlankValues( classes, VALUES_SEPARATOR );
369        
370        for( String className : classNames ) {
371          try {
372            Class<?> cls = Class.forName( className );
373            result.add( cls );
374          }
375          catch( ClassNotFoundException ex ) {
376            logger.fatal( ex.getMessage(), ex );
377            ServletConfigurationException e = ServletConfigurationException.forClassNotFound(className, ex ); 
378            throw e;
379          }
380        }
381        
382        return result;
383      }
384      
385      private static RequestType getFromRequestContentType( HttpServletRequest request ) {
386        assert request != null;
387        
388        String contentType = request.getContentType();
389        String contentTypeLowercase = "";
390        if( contentType != null ) {
391          contentTypeLowercase = contentType.toLowerCase();
392        }
393        
394        String pathInfo = request.getPathInfo();
395        
396        if( !StringUtils.isEmpty(pathInfo) && pathInfo.startsWith( PollRequestProcessor.PATHINFO_POLL_PREFIX)) {
397          return RequestType.POLL;
398        }
399        else if( contentTypeLowercase.startsWith( "application/json")) {
400          return RequestType.JSON;
401        }
402        else if( contentTypeLowercase.startsWith("application/x-www-form-urlencoded") && request.getMethod().toLowerCase().equals("post")) {
403          return RequestType.FORM_SIMPLE_POST;
404        }
405        else if( ServletFileUpload.isMultipartContent(request)) {
406          return RequestType.FORM_UPLOAD_POST;
407        }
408        else {
409          String requestInfo = ServletUtils.getDetailedRequestInformation(request);      
410          RequestException ex = RequestException.forRequestFormatNotRecognized();
411          logger.error( "Error during file uploader: " + ex.getMessage() + "\nAdditional request information: " + requestInfo, ex );
412          throw ex;
413        }
414      }
415    
416      @Override
417      public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
418        assert request != null;
419        assert response != null;
420        
421        doPost(request, response);
422      }
423    
424      @Override
425      public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
426        assert request != null;
427        assert response != null;
428        
429        NDC.push( "rid: " + Long.toString(getUniqueRequestId()) );
430        try {
431          Timer timer = new Timer();
432          try {
433            if( logger.isTraceEnabled()) {
434              String requestInfo = ServletUtils.getDetailedRequestInformation(request);
435              logger.trace( "Request info: " + requestInfo);
436            }        
437    
438            response.setContentType("text/html"); // MUST be "text/html" for uploads to work!
439            String requestEncoding = request.getCharacterEncoding();
440            // If we don't know what the request encoding is, assume it to be UTF-8
441            if( StringUtils.isEmpty(requestEncoding)) {
442              request.setCharacterEncoding(EncodingUtils.UTF8);
443            }
444            response.setCharacterEncoding(EncodingUtils.UTF8);
445    
446            RequestType type = getFromRequestContentType(request);
447            switch( type ) {
448            case FORM_SIMPLE_POST:
449              this.processor.processSimpleFormPostRequest( request.getReader(), response.getWriter() );
450              break;
451            case FORM_UPLOAD_POST:
452              processUploadFormPost(request, response);
453              break;
454            case JSON:
455              this.processor.processJsonRequest( request.getReader(), response.getWriter() );
456              break;
457            case POLL:
458              this.processor.processPollRequest( request.getReader(), response.getWriter(), request.getPathInfo() );
459              break;
460            }
461          }
462          finally {
463            timer.stop();
464            timer.logDebugTimeInMilliseconds("Total servlet processing time");
465          }
466        }
467        finally {
468          NDC.pop();
469        }
470      }
471    
472      private void processUploadFormPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
473        assert request != null;
474        assert response != null;
475        
476        try {
477          this.processor.processUploadFormPostRequest( getFileItems(request), response.getWriter() );
478        }
479        catch( FileUploadException e ) {
480          this.processor.handleFileUploadException( e );
481        }
482      }
483      
484      @SuppressWarnings("unchecked") // Unfortunately, parseRequest returns List, not List<FileItem>: define this wrapper method to avoid warnings
485      private List<FileItem> getFileItems(HttpServletRequest request) throws FileUploadException {
486        assert request != null;
487        
488        return this.uploader.parseRequest(request);
489      }
490    
491    }