001 /* 002 * Copyright © 2008, 2012 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 Lesser 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 Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser 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.HashMap; 031 import java.util.List; 032 import java.util.Map; 033 034 import javax.servlet.ServletConfig; 035 import javax.servlet.ServletException; 036 import javax.servlet.http.HttpServlet; 037 import javax.servlet.http.HttpServletRequest; 038 import javax.servlet.http.HttpServletResponse; 039 040 import org.apache.commons.fileupload.FileItem; 041 import org.apache.commons.fileupload.FileUploadException; 042 import org.apache.commons.fileupload.servlet.ServletFileUpload; 043 import org.apache.log4j.Logger; 044 import org.apache.log4j.NDC; 045 046 import com.softwarementors.extjs.djn.EncodingUtils; 047 import com.softwarementors.extjs.djn.StringUtils; 048 import com.softwarementors.extjs.djn.Timer; 049 import com.softwarementors.extjs.djn.api.Registry; 050 import com.softwarementors.extjs.djn.config.ApiConfiguration; 051 import com.softwarementors.extjs.djn.config.GlobalConfiguration; 052 import com.softwarementors.extjs.djn.gson.GsonBuilderConfigurator; 053 import com.softwarementors.extjs.djn.jscodegen.CodeFileGenerator; 054 import com.softwarementors.extjs.djn.router.RequestRouter; 055 import com.softwarementors.extjs.djn.router.RequestType; 056 import com.softwarementors.extjs.djn.router.dispatcher.Dispatcher; 057 import com.softwarementors.extjs.djn.router.dispatcher.DispatcherConfigurationException; 058 import com.softwarementors.extjs.djn.router.processor.RequestException; 059 import com.softwarementors.extjs.djn.router.processor.poll.PollRequestProcessor; 060 import com.softwarementors.extjs.djn.router.processor.standard.form.upload.UploadFormPostRequestProcessor; 061 import com.softwarementors.extjs.djn.router.processor.standard.json.JsonRequestProcessorThread; 062 import com.softwarementors.extjs.djn.scanner.Scanner; 063 import com.softwarementors.extjs.djn.servlet.config.RegistryConfigurationException; 064 import com.softwarementors.extjs.djn.servlet.ssm.SsmDispatcher; 065 import com.softwarementors.extjs.djn.servlet.ssm.SsmJsonRequestProcessorThread; 066 import com.softwarementors.extjs.djn.servlet.ssm.WebContextManager; 067 068 import edu.umd.cs.findbugs.annotations.CheckForNull; 069 import edu.umd.cs.findbugs.annotations.NonNull; 070 071 public class DirectJNgineServlet extends HttpServlet { 072 073 private static final long serialVersionUID = -5621879599626932408L; 074 075 @NonNull 076 private static final Logger logger = Logger.getLogger( DirectJNgineServlet.class); 077 078 /********************************************************* 079 * GlobalParameters and configuration 080 *********************************************************/ 081 private static final String VALUES_SEPARATOR = ","; 082 public static final String REGISTRY_CONFIGURATOR_CLASS = "registryConfiguratorClass"; 083 084 /* We handle processors and uploaders via a static map for several reasons: 085 086 1. Public fields are not ok: ServletFileUpload is simply not serializable, and 087 servlets are serializable. Besides, using servlet fields to hold state can 088 be problematic. 089 2. A single static variable is not ok, because it is possible to instantiate 090 a servlet several times via <servlet> entries in web.xml, each one having 091 a different configuration and therefore requiring a different RequestRouter and 092 ServletFileUpload. 093 094 The solution was to have static maps, keyed by servlet name, which is always 095 unique in a web application. 096 */ 097 @NonNull private static Map<String,RequestRouter> processors = new HashMap<String,RequestRouter>(); 098 @NonNull private static Map<String,ServletFileUpload> uploaders = new HashMap<String,ServletFileUpload>(); 099 100 // Non-mutable => no need to worry about thread-safety => can be an 'instance' variable 101 protected RequestRouter getProcessor() { 102 assert processors.containsKey(getServletName()); 103 104 return processors.get(getServletName()); 105 } 106 107 // Non-mutable => no need to worry about thread-safety => can be an 'instance' variable 108 protected ServletFileUpload getUploader() { 109 assert uploaders.containsKey(getServletName()); 110 111 return uploaders.get(getServletName()); 112 } 113 114 // This can be static: we do not worry if we get the same id 115 // in a load-balanced system 116 private static long id = 1000; // It is good for formatting to get lots of ids with the same number of digits... 117 118 public static class GlobalParameters { 119 @NonNull public static final String PROVIDERS_URL = "providersUrl"; 120 @NonNull public static final String DEBUG = "debug"; 121 122 @NonNull private static final String APIS_PARAMETER = "apis"; 123 @NonNull private static final String MINIFY = "minify"; 124 125 @NonNull public static final String BATCH_REQUESTS_MULTITHREADING_ENABLED = "batchRequestsMultithreadingEnabled"; 126 @NonNull public static final String BATCH_REQUESTS_MIN_THREADS_POOOL_SIZE = "batchRequestsMinThreadsPoolSize"; 127 @NonNull public static final String BATCH_REQUESTS_MAX_THREADS_POOOL_SIZE = "batchRequestsMaxThreadsPoolSize"; 128 @NonNull public static final String BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS = "batchRequestsMaxThreadKeepAliveSeconds"; 129 @NonNull public static final String BATCH_REQUESTS_MAX_THREADS_PER_REQUEST = "batchRequestsMaxThreadsPerRequest"; 130 @NonNull public static final String GSON_BUILDER_CONFIGURATOR_CLASS = "gsonBuilderConfiguratorClass"; 131 @NonNull public static final String DISPATCHER_CLASS = "dispatcherClass"; 132 @NonNull public static final String JSON_REQUEST_PROCESSOR_THREAD_CLASS = "jsonRequestProcessorThreadClass"; 133 @NonNull public static final String CONTEXT_PATH = "contextPath"; 134 @NonNull public static final String CREATE_SOURCE_FILES="createSourceFiles"; 135 } 136 137 public static class ApiParameters { 138 @NonNull public static final String API_FILE = "apiFile"; 139 @NonNull public static final String API_NAMESPACE = "apiNamespace"; 140 @NonNull public static final String ACTIONS_NAMESPACE = "actionsNamespace"; 141 @NonNull public static final String CLASSES = "classes"; 142 } 143 144 private static synchronized long getUniqueRequestId() { 145 return id++; 146 } 147 148 @Override 149 public void init(ServletConfig configuration) throws ServletException 150 { 151 assert configuration != null; 152 super.init(configuration); 153 154 Timer timer = new Timer(); 155 createDirectJNgineRouter(configuration); 156 timer.stop(); 157 timer.logDebugTimeInMilliseconds("Djn initialization: total DirectJNgine initialization time"); 158 } 159 160 protected void createDirectJNgineRouter(ServletConfig configuration) throws ServletException { 161 assert configuration != null; 162 163 Timer subtaskTimer = new Timer(); 164 GlobalConfiguration globalConfiguration = createGlobalConfiguration(configuration); 165 String registryConfiguratorClassName = ServletUtils.getParameter(configuration, REGISTRY_CONFIGURATOR_CLASS, null); 166 if( logger.isInfoEnabled() ) { 167 String value = registryConfiguratorClassName; 168 if( value == null) { 169 value = ""; 170 } 171 logger.info( "Servlet GLOBAL configuration: " + REGISTRY_CONFIGURATOR_CLASS + "=" + value ); 172 } 173 174 Class<? extends ServletRegistryConfigurator> registryConfiguratorClass = getRegistryConfiguratorClass(registryConfiguratorClassName); 175 List<ApiConfiguration> apiConfigurations = createApiConfigurationsFromServletConfigurationApi(configuration); 176 subtaskTimer.stop(); 177 subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Servlet Configuration Load time"); 178 179 subtaskTimer.restart(); 180 Registry registry = new Registry( globalConfiguration ); 181 Scanner scanner = new Scanner(registry); 182 scanner.scanAndRegisterApiConfigurations( apiConfigurations ); 183 subtaskTimer.stop(); 184 subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Standard Api processing time"); 185 186 if( registryConfiguratorClass != null ) { 187 subtaskTimer.restart(); 188 performCustomRegistryConfiguration( registryConfiguratorClass, registry, configuration ); 189 subtaskTimer.stop(); 190 subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Custom Registry processing time"); 191 } 192 193 subtaskTimer.restart(); 194 try { 195 CodeFileGenerator.updateSource(registry, globalConfiguration.getCreateSourceFiles()); 196 subtaskTimer.stop(); 197 subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Api Files creation time"); 198 } 199 catch( IOException ex ) { 200 ServletException e = new ServletException( "Unable to create DirectJNgine API files", ex ); 201 logger.fatal( e.getMessage(), e ); 202 throw e; 203 } 204 205 subtaskTimer.restart(); 206 207 initializeRouter(globalConfiguration, registry); 208 209 subtaskTimer.stop(); 210 subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Request Processor initialization time"); 211 } 212 213 private void initializeRouter(GlobalConfiguration globalConfiguration, Registry registry) { 214 String servletName = getServletName(); 215 uploaders.put( servletName, UploadFormPostRequestProcessor.createFileUploader() ); 216 processors.put( servletName, createRequestRouter(registry, globalConfiguration) ); 217 } 218 219 protected RequestRouter createRequestRouter(Registry registry, GlobalConfiguration globalConfiguration) { 220 assert registry != null; 221 assert globalConfiguration != null; 222 223 return new RequestRouter( registry, globalConfiguration, createDispatcher(globalConfiguration.getDispatcherClass()) ); 224 } 225 226 protected Dispatcher createDispatcher( Class<? extends Dispatcher> cls ) { 227 assert cls != null; 228 try { 229 return cls.newInstance(); 230 } 231 catch (InstantiationException e) { 232 DispatcherConfigurationException ex = DispatcherConfigurationException.forUnableToInstantiateDispatcher(cls, e); 233 logger.fatal( ex.getMessage(), ex); 234 throw ex; 235 } 236 catch (IllegalAccessException e) { 237 DispatcherConfigurationException ex = DispatcherConfigurationException.forUnableToInstantiateDispatcher(cls, e); 238 logger.fatal( ex.getMessage(), ex); 239 throw ex; 240 } 241 } 242 243 protected void performCustomRegistryConfiguration(Class<? extends ServletRegistryConfigurator> configuratorClass, Registry registry, ServletConfig config) { 244 ServletRegistryConfigurator registryConfigurator = createCustomRegistryConfigurator( configuratorClass ); // registry.getGlobalConfiguration().getRegistryConfiguratorClass() ).configure( registry, configuration); 245 registryConfigurator.configure(registry, config); 246 } 247 248 private static ServletRegistryConfigurator createCustomRegistryConfigurator( Class<? extends ServletRegistryConfigurator> configuratorClass ) { 249 assert configuratorClass != null; 250 251 try { 252 return configuratorClass.newInstance(); 253 } 254 catch (InstantiationException e) { 255 RegistryConfigurationException ex = RegistryConfigurationException.forUnableToInstantiateRegistryConfigurator(configuratorClass, e); 256 logger.fatal( ex.getMessage(), ex); 257 throw ex; 258 } 259 catch (IllegalAccessException e) { 260 RegistryConfigurationException ex = RegistryConfigurationException.forUnableToInstantiateRegistryConfigurator(configuratorClass, e); 261 logger.fatal( ex.getMessage(), ex); 262 throw ex; 263 } 264 } 265 266 protected GlobalConfiguration createGlobalConfiguration(ServletConfig configuration) { 267 assert configuration != null; 268 269 ServletUtils.checkRequiredParameters(configuration, GlobalParameters.PROVIDERS_URL); 270 271 boolean isDebug = ServletUtils.getBooleanParameter( configuration, GlobalParameters.DEBUG, GlobalConfiguration.DEFAULT_DEBUG_VALUE); 272 String providersUrl = ServletUtils.getRequiredParameter(configuration, GlobalParameters.PROVIDERS_URL); 273 String gsonConfiguratorClassName = ServletUtils.getParameter(configuration, GlobalParameters.GSON_BUILDER_CONFIGURATOR_CLASS, GlobalConfiguration.DEFAULT_GSON_BUILDER_CONFIGURATOR_CLASS.getName()); 274 String dispatcherClassName = ServletUtils.getParameter(configuration, GlobalParameters.DISPATCHER_CLASS, /*GlobalConfiguration.DEFAULT_DISPATCHER_CLASS.getName()*/ getDefaultDispatcherClass().getName()); 275 String jsonRequestProcessorThreadClassName = ServletUtils.getParameter(configuration, GlobalParameters.JSON_REQUEST_PROCESSOR_THREAD_CLASS, /*GlobalConfiguration.DEFAULT_JSON_REQUEST_PROCESSOR_THREAD_CLASS.getName()*/ getDefaultJsonRequestProcessoThreadClass().getName()); 276 277 // Global multithreaded-batched requests support parameters 278 boolean isBatchRequestsMultithreadingEnabled = ServletUtils.getBooleanParameter( configuration, GlobalParameters.BATCH_REQUESTS_MULTITHREADING_ENABLED, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MULTITHREADING_ENABLED_VALUE); 279 boolean minifyEnabled = ServletUtils.getBooleanParameter( configuration, GlobalParameters.MINIFY, GlobalConfiguration.DEFAULT_MINIFY_VALUE); 280 281 int batchRequestsMinThreadsPoolSize = ServletUtils.getIntParameterGreaterOrEqualToValue( 282 configuration, GlobalParameters.BATCH_REQUESTS_MIN_THREADS_POOOL_SIZE, 283 GlobalConfiguration.MIN_BATCH_REQUESTS_MIN_THREAD_POOL_SIZE, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MIN_THREAD_POOL_SIZE); 284 int batchRequestsMaxThreadsPoolSize = ServletUtils.getIntParameterGreaterOrEqualToValue( 285 configuration, GlobalParameters.BATCH_REQUESTS_MAX_THREADS_POOOL_SIZE, 286 GlobalConfiguration.MIN_BATCH_REQUESTS_MAX_THREAD_POOL_SIZE, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MAX_THREAD_POOL_SIZE); 287 int batchRequestsThreadKeepAliveSeconds = ServletUtils.getIntParameterGreaterOrEqualToValue( 288 configuration, GlobalParameters.BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS, 289 GlobalConfiguration.MIN_BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS); 290 int batchRequestsMaxThreadsPerRequest = ServletUtils.getIntParameterGreaterOrEqualToValue( 291 configuration, GlobalParameters.BATCH_REQUESTS_MAX_THREADS_PER_REQUEST, 292 GlobalConfiguration.MIN_BATCH_REQUESTS_MAX_THREADS_PER_REQUEST, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MAX_THREADS_PER_REQUEST); 293 String contextPath = configuration.getInitParameter( GlobalParameters.CONTEXT_PATH ); 294 boolean createSourceFiles = ServletUtils.getBooleanParameter(configuration, GlobalParameters.CREATE_SOURCE_FILES, GlobalConfiguration.DEFAULT_CREATE_SOURCE_FILES); 295 296 if( batchRequestsMinThreadsPoolSize > batchRequestsMaxThreadsPoolSize ) { 297 ServletConfigurationException ex = ServletConfigurationException.forMaxThreadPoolSizeMustBeEqualOrGreaterThanMinThreadPoolSize(batchRequestsMinThreadsPoolSize, batchRequestsMaxThreadsPoolSize); 298 logger.fatal( ex.getMessage(), ex ); 299 throw ex; 300 } 301 302 if( logger.isInfoEnabled() ) { 303 String contextPathInfo = contextPath; 304 if( contextPathInfo == null ) { 305 contextPathInfo = "--not specified: calculated via Javascript--"; 306 } 307 logger.info( "Servlet GLOBAL configuration: " + 308 GlobalParameters.DEBUG + "=" + isDebug + ", " + 309 GlobalParameters.PROVIDERS_URL + "=" + providersUrl + ", " + 310 GlobalParameters.MINIFY + "=" + minifyEnabled + ", " + 311 GlobalParameters.BATCH_REQUESTS_MULTITHREADING_ENABLED + "=" + isBatchRequestsMultithreadingEnabled + ", " + 312 GlobalParameters.BATCH_REQUESTS_MIN_THREADS_POOOL_SIZE + "=" + batchRequestsMinThreadsPoolSize + ", " + 313 GlobalParameters.BATCH_REQUESTS_MAX_THREADS_POOOL_SIZE + "=" + batchRequestsMaxThreadsPoolSize + ", " + 314 GlobalParameters.BATCH_REQUESTS_MAX_THREADS_PER_REQUEST + "=" + batchRequestsMaxThreadsPerRequest + ", " + 315 GlobalParameters.BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS + "=" + batchRequestsThreadKeepAliveSeconds + ", " + 316 GlobalParameters.GSON_BUILDER_CONFIGURATOR_CLASS + "=" + gsonConfiguratorClassName + ", " + 317 GlobalParameters.DISPATCHER_CLASS + "=" + dispatcherClassName + ", " + 318 GlobalParameters.JSON_REQUEST_PROCESSOR_THREAD_CLASS + "=" + jsonRequestProcessorThreadClassName + ", " + 319 GlobalParameters.CONTEXT_PATH + "=" + contextPathInfo + ", " + 320 GlobalParameters.CREATE_SOURCE_FILES + "=" + createSourceFiles 321 ); 322 } 323 324 Class<? extends GsonBuilderConfigurator> gsonConfiguratorClass = getGsonBuilderConfiguratorClass(gsonConfiguratorClassName); 325 Class<? extends Dispatcher> dispatcherClass = getDispatcherClass(dispatcherClassName); 326 Class<? extends JsonRequestProcessorThread> jsonRequestProcessorClass = getJsonRequestProcessorThreadClass(jsonRequestProcessorThreadClassName); 327 328 GlobalConfiguration result = new GlobalConfiguration( 329 contextPath, 330 providersUrl, isDebug, 331 gsonConfiguratorClass, jsonRequestProcessorClass, dispatcherClass, 332 minifyEnabled, 333 isBatchRequestsMultithreadingEnabled, batchRequestsMinThreadsPoolSize, batchRequestsMaxThreadsPoolSize, 334 batchRequestsThreadKeepAliveSeconds, batchRequestsMaxThreadsPerRequest, 335 createSourceFiles); 336 return result; 337 } 338 339 private static Class<? extends JsonRequestProcessorThread> getDefaultJsonRequestProcessoThreadClass() { 340 return SsmJsonRequestProcessorThread.class; 341 } 342 343 private static Class<? extends Dispatcher> getDefaultDispatcherClass() { 344 return SsmDispatcher.class; 345 } 346 347 @SuppressWarnings("unchecked") 348 private static Class<? extends GsonBuilderConfigurator> getGsonBuilderConfiguratorClass(String gsonConfiguratorClassName) { 349 assert !StringUtils.isEmpty(gsonConfiguratorClassName); 350 351 Class<? extends GsonBuilderConfigurator> configuratorClass; 352 try { 353 configuratorClass = (Class<GsonBuilderConfigurator>)Class.forName(gsonConfiguratorClassName); 354 if( !GsonBuilderConfigurator.class.isAssignableFrom(configuratorClass)) { 355 ServletConfigurationException ex = ServletConfigurationException.forGsonBuilderConfiguratorMustImplementGsonBuilderConfiguratorInterface(gsonConfiguratorClassName ); 356 logger.fatal( ex.getMessage(), ex ); 357 throw ex; 358 } 359 return configuratorClass; 360 } 361 catch( ClassNotFoundException ex ) { 362 ServletConfigurationException e = ServletConfigurationException.forClassNotFound(gsonConfiguratorClassName, ex ); 363 logger.fatal( e.getMessage(), e ); 364 throw e; 365 } 366 } 367 368 @SuppressWarnings("unchecked") 369 private static Class<? extends Dispatcher> getDispatcherClass(String dispatcherClassName) { 370 assert !StringUtils.isEmpty(dispatcherClassName); 371 372 Class<? extends Dispatcher> configuratorClass; 373 try { 374 configuratorClass = (Class<Dispatcher>)Class.forName(dispatcherClassName); 375 if( !Dispatcher.class.isAssignableFrom(configuratorClass)) { 376 ServletConfigurationException ex = ServletConfigurationException.forDispatcherMustImplementDispatcherInterface(dispatcherClassName ); 377 logger.fatal( ex.getMessage(), ex ); 378 throw ex; 379 } 380 return configuratorClass; 381 } 382 catch( ClassNotFoundException ex ) { 383 ServletConfigurationException e = ServletConfigurationException.forClassNotFound(dispatcherClassName, ex ); 384 logger.fatal( e.getMessage(), e ); 385 throw e; 386 } 387 } 388 389 @SuppressWarnings("unchecked") 390 private static Class<? extends JsonRequestProcessorThread> getJsonRequestProcessorThreadClass(String jsonRequestProcessorThreadClassName) { 391 assert !StringUtils.isEmpty(jsonRequestProcessorThreadClassName); 392 393 Class<? extends JsonRequestProcessorThread> cls; 394 try { 395 cls = (Class<JsonRequestProcessorThread>)Class.forName(jsonRequestProcessorThreadClassName); 396 if( !JsonRequestProcessorThread.class.isAssignableFrom(cls)) { 397 ServletConfigurationException ex = ServletConfigurationException.forJsonRequestProcessorThreadMustImplementJsonRequestProcessorThreadInterface(jsonRequestProcessorThreadClassName ); 398 logger.fatal( ex.getMessage(), ex ); 399 throw ex; 400 } 401 return cls; 402 } 403 catch( ClassNotFoundException ex ) { 404 ServletConfigurationException e = ServletConfigurationException.forClassNotFound(jsonRequestProcessorThreadClassName, ex ); 405 logger.fatal( e.getMessage(), e ); 406 throw e; 407 } 408 } 409 410 @SuppressWarnings("unchecked") 411 @CheckForNull private static Class<? extends ServletRegistryConfigurator> getRegistryConfiguratorClass(String registryConfiguratorClassName) { 412 if( StringUtils.isEmpty(registryConfiguratorClassName)) { 413 return null; 414 } 415 416 Class<? extends ServletRegistryConfigurator> configuratorClass; 417 try { 418 configuratorClass = (Class<ServletRegistryConfigurator>)Class.forName(registryConfiguratorClassName); 419 if( !ServletRegistryConfigurator.class.isAssignableFrom(configuratorClass)) { 420 ServletConfigurationException ex = ServletConfigurationException.forRegistryConfiguratorMustImplementGsonBuilderConfiguratorInterface(registryConfiguratorClassName ); 421 logger.fatal( ex.getMessage(), ex ); 422 throw ex; 423 } 424 return configuratorClass; 425 } 426 catch( ClassNotFoundException ex ) { 427 ServletConfigurationException e = ServletConfigurationException.forClassNotFound(registryConfiguratorClassName, ex ); 428 logger.fatal( e.getMessage(), e ); 429 throw e; 430 } 431 } 432 433 protected List<ApiConfiguration> createApiConfigurationsFromServletConfigurationApi(ServletConfig configuration) { 434 assert configuration != null; 435 436 List<ApiConfiguration> result = new ArrayList<ApiConfiguration>(); 437 String apisParameter = ServletUtils.getRequiredParameter(configuration, GlobalParameters.APIS_PARAMETER); 438 List<String> apis = StringUtils.getNonBlankValues(apisParameter, VALUES_SEPARATOR); 439 logger.info( "Servlet APIs configuration: " + GlobalParameters.APIS_PARAMETER + "=" + apisParameter ); 440 441 for( String api : apis) { 442 ApiConfiguration apiConfiguration = createApiConfigurationFromServletConfigurationApi( configuration, api ); 443 result.add( apiConfiguration ); 444 } 445 446 if( result.isEmpty() ) { 447 logger.warn( "No apis specified"); 448 } 449 450 return result; 451 } 452 453 private ApiConfiguration createApiConfigurationFromServletConfigurationApi(ServletConfig configuration, String api) { 454 assert configuration != null; 455 assert !StringUtils.isEmpty(api); 456 457 String apiFile = ServletUtils.getParameter( configuration, api + "." + ApiParameters.API_FILE, api + ApiConfiguration.DEFAULT_API_FILE_SUFFIX ); 458 String fullGeneratedApiFile = getServletContext().getRealPath(apiFile); 459 String apiNamespace = ServletUtils.getParameter( configuration, api + "." + ApiParameters.API_NAMESPACE, "" ); 460 assert apiNamespace != null; 461 String actionsNamespace = ServletUtils.getParameter( configuration, api + "." + ApiParameters.ACTIONS_NAMESPACE, "" ); 462 463 // If apiNamespace is empty, try to use actionsNamespace: if still empty, use the api name itself 464 if( apiNamespace.equals("")) { 465 if( actionsNamespace.equals("")) { 466 apiNamespace = ApiConfiguration.DEFAULT_NAMESPACE_PREFIX + api; 467 if( logger.isDebugEnabled() ) { 468 logger.debug( "Using the api name, prefixed with '" + ApiConfiguration.DEFAULT_NAMESPACE_PREFIX + "' as the value for " + ApiParameters.API_NAMESPACE); 469 } 470 } 471 else { 472 apiNamespace = actionsNamespace; 473 logger.debug( "Using " + ApiParameters.ACTIONS_NAMESPACE + " as the value for " + ApiParameters.API_NAMESPACE); 474 } 475 } 476 477 String classNames = ServletUtils.getParameter( configuration, api + "." + ApiParameters.CLASSES, "" ); 478 List<Class<?>> classes = getClasses( classNames ); 479 480 if( logger.isInfoEnabled() ) { 481 logger.info( "Servlet '" + api + "' Api configuration: " + 482 ApiParameters.API_NAMESPACE + "=" + apiNamespace + ", " + 483 ApiParameters.ACTIONS_NAMESPACE + "=" + actionsNamespace + ", " + 484 ApiParameters.API_FILE + "=" + apiFile + " => Full api file: " + fullGeneratedApiFile + ", " + 485 ApiParameters.CLASSES + "=" + classNames); 486 } 487 488 if( classes.isEmpty() ) { 489 logger.warn( "There are no action classes to register for api '" + api + "'"); 490 } 491 ApiConfiguration apiConfiguration = new ApiConfiguration( api, apiFile, fullGeneratedApiFile, apiNamespace, actionsNamespace, classes ); 492 493 return apiConfiguration; 494 } 495 496 private static List<Class<?>> getClasses( String classes ) { 497 assert classes != null; 498 499 List<Class<?>> result = new ArrayList<Class<?>>(); 500 if( StringUtils.isEmpty(classes) ) { 501 return result; 502 } 503 List<String> classNames = StringUtils.getNonBlankValues( classes, VALUES_SEPARATOR ); 504 505 for( String className : classNames ) { 506 try { 507 Class<?> cls = Class.forName( className ); 508 result.add( cls ); 509 } 510 catch( ClassNotFoundException ex ) { 511 logger.fatal( ex.getMessage(), ex ); 512 ServletConfigurationException e = ServletConfigurationException.forClassNotFound(className, ex ); 513 throw e; 514 } 515 } 516 517 return result; 518 } 519 520 private static RequestType getFromRequestContentType( HttpServletRequest request ) { 521 assert request != null; 522 523 String contentType = request.getContentType(); 524 String pathInfo = request.getPathInfo(); 525 526 if( !StringUtils.isEmpty(pathInfo) && pathInfo.startsWith( PollRequestProcessor.PATHINFO_POLL_PREFIX)) { 527 return RequestType.POLL; 528 } 529 else if( StringUtils.startsWithCaseInsensitive( contentType, "application/json") ) { 530 return RequestType.JSON; 531 } 532 else if( StringUtils.startsWithCaseInsensitive( contentType, "application/x-www-form-urlencoded") && request.getMethod().equalsIgnoreCase("post")) { 533 return RequestType.FORM_SIMPLE_POST; 534 } 535 else if( ServletFileUpload.isMultipartContent(request)) { 536 return RequestType.FORM_UPLOAD_POST; 537 } 538 else if( RequestRouter.isSourceRequest(pathInfo)) { 539 return RequestType.SOURCE; 540 } 541 else { 542 String requestInfo = ServletUtils.getDetailedRequestInformation(request); 543 RequestException ex = RequestException.forRequestFormatNotRecognized(); 544 logger.error( "Error during file uploader: " + ex.getMessage() + "\nAdditional request information: " + requestInfo, ex ); 545 throw ex; 546 } 547 } 548 549 @Override 550 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 551 assert request != null; 552 assert response != null; 553 doPost(request, response); 554 } 555 556 @Override 557 public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { 558 assert request != null; 559 assert response != null; 560 561 NDC.push( "rid=" + Long.toString(getUniqueRequestId()) ); 562 try { 563 Timer timer = new Timer(); 564 try { 565 attachThreadLocalData( request, response); 566 try { 567 if( logger.isTraceEnabled()) { 568 String requestInfo = ServletUtils.getDetailedRequestInformation(request); 569 logger.trace( "Request info: " + requestInfo); 570 } 571 572 String requestEncoding = request.getCharacterEncoding(); 573 // If we don't know what the request encoding is, assume it to be UTF-8 574 if( StringUtils.isEmpty(requestEncoding)) { 575 request.setCharacterEncoding(EncodingUtils.UTF8); 576 } 577 response.setCharacterEncoding(EncodingUtils.UTF8); 578 579 RequestType type = getFromRequestContentType(request); 580 processRequest(request, response, type); 581 } 582 finally { 583 detachThreadLocalData(); 584 } 585 } 586 finally { 587 timer.stop(); 588 timer.logDebugTimeInMilliseconds("Total servlet processing time"); 589 } 590 591 } 592 finally { 593 NDC.pop(); 594 } 595 } 596 597 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="SF_SWITCH_NO_DEFAULT", 598 justification="Missing a 'default' branch is not a problem with enums, given the appropriate compiler settings") 599 private void processRequest(HttpServletRequest request, HttpServletResponse response, RequestType type) 600 throws IOException { 601 final String JSON_CONTENT_TYPE = "application/json"; 602 final String JAVASCRIPT_CONTENT_TYPE = "text/javascript"; // *YES*, shoul be "application/javascript", but then there is IE, and the fact that this is really cross-browser (sigh!) 603 final String HTML_CONTENT_TYPE = "text/html"; 604 605 RequestRouter processor = getProcessor(); 606 switch( type ) { 607 case FORM_SIMPLE_POST: 608 response.setContentType(JSON_CONTENT_TYPE); 609 processor.processSimpleFormPostRequest( request.getReader(), response.getWriter() ); 610 break; 611 case FORM_UPLOAD_POST: 612 response.setContentType(HTML_CONTENT_TYPE); // MUST be "text/html" for uploads to work! 613 processUploadFormPost(request, response); 614 break; 615 case JSON: 616 response.setContentType(JSON_CONTENT_TYPE); 617 processor.processJsonRequest( request.getReader(), response.getWriter() ); 618 break; 619 case POLL: 620 response.setContentType(JSON_CONTENT_TYPE); 621 processor.processPollRequest( request.getReader(), response.getWriter(), request.getPathInfo() ); 622 break; 623 case SOURCE: 624 response.setContentType(JAVASCRIPT_CONTENT_TYPE); 625 processor.processSourceRequest( request.getReader(), response.getWriter(), request.getPathInfo()); 626 break; 627 } 628 } 629 630 protected void attachThreadLocalData( HttpServletRequest request, HttpServletResponse response) { 631 WebContextManager.initializeWebContextForCurrentThread(this, request, response); 632 } 633 634 protected void detachThreadLocalData() { 635 WebContextManager.detachFromCurrentThread(); 636 } 637 638 private void processUploadFormPost(HttpServletRequest request, HttpServletResponse response) throws IOException { 639 assert request != null; 640 assert response != null; 641 642 RequestRouter router = getProcessor(); 643 UploadFormPostRequestProcessor processor = router.createUploadFromProcessor(); 644 try { 645 router.processUploadFormPostRequest( processor, getFileItems(request), response.getWriter() ); 646 } 647 catch( FileUploadException e ) { 648 processor.handleFileUploadException( e ); 649 } 650 } 651 652 @SuppressWarnings("unchecked") 653 private List<FileItem> getFileItems(HttpServletRequest request) throws FileUploadException { 654 assert request != null; 655 656 ServletFileUpload uploader = getUploader(); 657 return uploader.parseRequest(request); 658 } 659 660 }