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 }