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