/* * Copyright © 2008, 2012 Pedro Agulló Soliveres. * * This file is part of DirectJNgine. * * DirectJNgine is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License. * * Commercial use is permitted to the extent that the code/component(s) * do NOT become part of another Open Source or Commercially developed * licensed development library or toolkit without explicit permission. * * DirectJNgine is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with DirectJNgine. If not, see <http://www.gnu.org/licenses/>. * * This software uses the ExtJs library (http://extjs.com), which is * distributed under the GPL v3 license (see http://extjs.com/license). */ package com.softwarementors.extjs.djn.servlet; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.log4j.Logger; import org.apache.log4j.NDC; import com.softwarementors.extjs.djn.EncodingUtils; import com.softwarementors.extjs.djn.StringUtils; import com.softwarementors.extjs.djn.Timer; import com.softwarementors.extjs.djn.api.Registry; import com.softwarementors.extjs.djn.config.ApiConfiguration; import com.softwarementors.extjs.djn.config.GlobalConfiguration; import com.softwarementors.extjs.djn.gson.GsonBuilderConfigurator; import com.softwarementors.extjs.djn.jscodegen.CodeFileGenerator; import com.softwarementors.extjs.djn.router.RequestRouter; import com.softwarementors.extjs.djn.router.RequestType; import com.softwarementors.extjs.djn.router.dispatcher.Dispatcher; import com.softwarementors.extjs.djn.router.dispatcher.DispatcherConfigurationException; import com.softwarementors.extjs.djn.router.processor.RequestException; import com.softwarementors.extjs.djn.router.processor.poll.PollRequestProcessor; import com.softwarementors.extjs.djn.router.processor.standard.form.upload.UploadFormPostRequestProcessor; import com.softwarementors.extjs.djn.router.processor.standard.json.JsonRequestProcessorThread; import com.softwarementors.extjs.djn.scanner.Scanner; import com.softwarementors.extjs.djn.servlet.config.RegistryConfigurationException; import com.softwarementors.extjs.djn.servlet.ssm.SsmDispatcher; import com.softwarementors.extjs.djn.servlet.ssm.SsmJsonRequestProcessorThread; import com.softwarementors.extjs.djn.servlet.ssm.WebContextManager; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; public class DirectJNgineServlet extends HttpServlet { private static final long serialVersionUID = -5621879599626932408L; @NonNull private static final Logger logger = Logger.getLogger( DirectJNgineServlet.class); /********************************************************* * GlobalParameters and configuration *********************************************************/ private static final String VALUES_SEPARATOR = ","; public static final String REGISTRY_CONFIGURATOR_CLASS = "registryConfiguratorClass"; /* We handle processors and uploaders via a static map for several reasons: 1. Public fields are not ok: ServletFileUpload is simply not serializable, and servlets are serializable. Besides, using servlet fields to hold state can be problematic. 2. A single static variable is not ok, because it is possible to instantiate a servlet several times via <servlet> entries in web.xml, each one having a different configuration and therefore requiring a different RequestRouter and ServletFileUpload. The solution was to have static maps, keyed by servlet name, which is always unique in a web application. */ @NonNull private static Map<String,RequestRouter> processors = new HashMap<String,RequestRouter>(); @NonNull private static Map<String,ServletFileUpload> uploaders = new HashMap<String,ServletFileUpload>(); // Non-mutable => no need to worry about thread-safety => can be an 'instance' variable protected RequestRouter getProcessor() { assert processors.containsKey(getServletName()); return processors.get(getServletName()); } // Non-mutable => no need to worry about thread-safety => can be an 'instance' variable protected ServletFileUpload getUploader() { assert uploaders.containsKey(getServletName()); return uploaders.get(getServletName()); } // This can be static: we do not worry if we get the same id // in a load-balanced system private static long id = 1000; // It is good for formatting to get lots of ids with the same number of digits... public static class GlobalParameters { @NonNull public static final String PROVIDERS_URL = "providersUrl"; @NonNull public static final String DEBUG = "debug"; @NonNull private static final String APIS_PARAMETER = "apis"; @NonNull private static final String MINIFY = "minify"; @NonNull public static final String BATCH_REQUESTS_MULTITHREADING_ENABLED = "batchRequestsMultithreadingEnabled"; @NonNull public static final String BATCH_REQUESTS_MIN_THREADS_POOOL_SIZE = "batchRequestsMinThreadsPoolSize"; @NonNull public static final String BATCH_REQUESTS_MAX_THREADS_POOOL_SIZE = "batchRequestsMaxThreadsPoolSize"; @NonNull public static final String BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS = "batchRequestsMaxThreadKeepAliveSeconds"; @NonNull public static final String BATCH_REQUESTS_MAX_THREADS_PER_REQUEST = "batchRequestsMaxThreadsPerRequest"; @NonNull public static final String GSON_BUILDER_CONFIGURATOR_CLASS = "gsonBuilderConfiguratorClass"; @NonNull public static final String DISPATCHER_CLASS = "dispatcherClass"; @NonNull public static final String JSON_REQUEST_PROCESSOR_THREAD_CLASS = "jsonRequestProcessorThreadClass"; @NonNull public static final String CONTEXT_PATH = "contextPath"; @NonNull public static final String CREATE_SOURCE_FILES="createSourceFiles"; } public static class ApiParameters { @NonNull public static final String API_FILE = "apiFile"; @NonNull public static final String API_NAMESPACE = "apiNamespace"; @NonNull public static final String ACTIONS_NAMESPACE = "actionsNamespace"; @NonNull public static final String CLASSES = "classes"; } private static synchronized long getUniqueRequestId() { return id++; } @Override public void init(ServletConfig configuration) throws ServletException { assert configuration != null; super.init(configuration); Timer timer = new Timer(); createDirectJNgineRouter(configuration); timer.stop(); timer.logDebugTimeInMilliseconds("Djn initialization: total DirectJNgine initialization time"); } protected void createDirectJNgineRouter(ServletConfig configuration) throws ServletException { assert configuration != null; Timer subtaskTimer = new Timer(); GlobalConfiguration globalConfiguration = createGlobalConfiguration(configuration); String registryConfiguratorClassName = ServletUtils.getParameter(configuration, REGISTRY_CONFIGURATOR_CLASS, null); if( logger.isInfoEnabled() ) { String value = registryConfiguratorClassName; if( value == null) { value = ""; } logger.info( "Servlet GLOBAL configuration: " + REGISTRY_CONFIGURATOR_CLASS + "=" + value ); } Class<? extends ServletRegistryConfigurator> registryConfiguratorClass = getRegistryConfiguratorClass(registryConfiguratorClassName); List<ApiConfiguration> apiConfigurations = createApiConfigurationsFromServletConfigurationApi(configuration); subtaskTimer.stop(); subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Servlet Configuration Load time"); subtaskTimer.restart(); Registry registry = new Registry( globalConfiguration ); Scanner scanner = new Scanner(registry); scanner.scanAndRegisterApiConfigurations( apiConfigurations ); subtaskTimer.stop(); subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Standard Api processing time"); if( registryConfiguratorClass != null ) { subtaskTimer.restart(); performCustomRegistryConfiguration( registryConfiguratorClass, registry, configuration ); subtaskTimer.stop(); subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Custom Registry processing time"); } subtaskTimer.restart(); try { CodeFileGenerator.updateSource(registry, globalConfiguration.getCreateSourceFiles()); subtaskTimer.stop(); subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Api Files creation time"); } catch( IOException ex ) { ServletException e = new ServletException( "Unable to create DirectJNgine API files", ex ); logger.fatal( e.getMessage(), e ); throw e; } subtaskTimer.restart(); initializeRouter(globalConfiguration, registry); subtaskTimer.stop(); subtaskTimer.logDebugTimeInMilliseconds("Djn initialization: Request Processor initialization time"); } private void initializeRouter(GlobalConfiguration globalConfiguration, Registry registry) { String servletName = getServletName(); uploaders.put( servletName, UploadFormPostRequestProcessor.createFileUploader() ); processors.put( servletName, createRequestRouter(registry, globalConfiguration) ); } protected RequestRouter createRequestRouter(Registry registry, GlobalConfiguration globalConfiguration) { assert registry != null; assert globalConfiguration != null; return new RequestRouter( registry, globalConfiguration, createDispatcher(globalConfiguration.getDispatcherClass()) ); } protected Dispatcher createDispatcher( Class<? extends Dispatcher> cls ) { assert cls != null; try { return cls.newInstance(); } catch (InstantiationException e) { DispatcherConfigurationException ex = DispatcherConfigurationException.forUnableToInstantiateDispatcher(cls, e); logger.fatal( ex.getMessage(), ex); throw ex; } catch (IllegalAccessException e) { DispatcherConfigurationException ex = DispatcherConfigurationException.forUnableToInstantiateDispatcher(cls, e); logger.fatal( ex.getMessage(), ex); throw ex; } } protected void performCustomRegistryConfiguration(Class<? extends ServletRegistryConfigurator> configuratorClass, Registry registry, ServletConfig config) { ServletRegistryConfigurator registryConfigurator = createCustomRegistryConfigurator( configuratorClass ); // registry.getGlobalConfiguration().getRegistryConfiguratorClass() ).configure( registry, configuration); registryConfigurator.configure(registry, config); } private static ServletRegistryConfigurator createCustomRegistryConfigurator( Class<? extends ServletRegistryConfigurator> configuratorClass ) { assert configuratorClass != null; try { return configuratorClass.newInstance(); } catch (InstantiationException e) { RegistryConfigurationException ex = RegistryConfigurationException.forUnableToInstantiateRegistryConfigurator(configuratorClass, e); logger.fatal( ex.getMessage(), ex); throw ex; } catch (IllegalAccessException e) { RegistryConfigurationException ex = RegistryConfigurationException.forUnableToInstantiateRegistryConfigurator(configuratorClass, e); logger.fatal( ex.getMessage(), ex); throw ex; } } protected GlobalConfiguration createGlobalConfiguration(ServletConfig configuration) { assert configuration != null; ServletUtils.checkRequiredParameters(configuration, GlobalParameters.PROVIDERS_URL); boolean isDebug = ServletUtils.getBooleanParameter( configuration, GlobalParameters.DEBUG, GlobalConfiguration.DEFAULT_DEBUG_VALUE); String providersUrl = ServletUtils.getRequiredParameter(configuration, GlobalParameters.PROVIDERS_URL); String gsonConfiguratorClassName = ServletUtils.getParameter(configuration, GlobalParameters.GSON_BUILDER_CONFIGURATOR_CLASS, GlobalConfiguration.DEFAULT_GSON_BUILDER_CONFIGURATOR_CLASS.getName()); String dispatcherClassName = ServletUtils.getParameter(configuration, GlobalParameters.DISPATCHER_CLASS, /*GlobalConfiguration.DEFAULT_DISPATCHER_CLASS.getName()*/ getDefaultDispatcherClass().getName()); String jsonRequestProcessorThreadClassName = ServletUtils.getParameter(configuration, GlobalParameters.JSON_REQUEST_PROCESSOR_THREAD_CLASS, /*GlobalConfiguration.DEFAULT_JSON_REQUEST_PROCESSOR_THREAD_CLASS.getName()*/ getDefaultJsonRequestProcessoThreadClass().getName()); // Global multithreaded-batched requests support parameters boolean isBatchRequestsMultithreadingEnabled = ServletUtils.getBooleanParameter( configuration, GlobalParameters.BATCH_REQUESTS_MULTITHREADING_ENABLED, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MULTITHREADING_ENABLED_VALUE); boolean minifyEnabled = ServletUtils.getBooleanParameter( configuration, GlobalParameters.MINIFY, GlobalConfiguration.DEFAULT_MINIFY_VALUE); int batchRequestsMinThreadsPoolSize = ServletUtils.getIntParameterGreaterOrEqualToValue( configuration, GlobalParameters.BATCH_REQUESTS_MIN_THREADS_POOOL_SIZE, GlobalConfiguration.MIN_BATCH_REQUESTS_MIN_THREAD_POOL_SIZE, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MIN_THREAD_POOL_SIZE); int batchRequestsMaxThreadsPoolSize = ServletUtils.getIntParameterGreaterOrEqualToValue( configuration, GlobalParameters.BATCH_REQUESTS_MAX_THREADS_POOOL_SIZE, GlobalConfiguration.MIN_BATCH_REQUESTS_MAX_THREAD_POOL_SIZE, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MAX_THREAD_POOL_SIZE); int batchRequestsThreadKeepAliveSeconds = ServletUtils.getIntParameterGreaterOrEqualToValue( configuration, GlobalParameters.BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS, GlobalConfiguration.MIN_BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS); int batchRequestsMaxThreadsPerRequest = ServletUtils.getIntParameterGreaterOrEqualToValue( configuration, GlobalParameters.BATCH_REQUESTS_MAX_THREADS_PER_REQUEST, GlobalConfiguration.MIN_BATCH_REQUESTS_MAX_THREADS_PER_REQUEST, GlobalConfiguration.DEFAULT_BATCH_REQUESTS_MAX_THREADS_PER_REQUEST); String contextPath = configuration.getInitParameter( GlobalParameters.CONTEXT_PATH ); boolean createSourceFiles = ServletUtils.getBooleanParameter(configuration, GlobalParameters.CREATE_SOURCE_FILES, GlobalConfiguration.DEFAULT_CREATE_SOURCE_FILES); if( batchRequestsMinThreadsPoolSize > batchRequestsMaxThreadsPoolSize ) { ServletConfigurationException ex = ServletConfigurationException.forMaxThreadPoolSizeMustBeEqualOrGreaterThanMinThreadPoolSize(batchRequestsMinThreadsPoolSize, batchRequestsMaxThreadsPoolSize); logger.fatal( ex.getMessage(), ex ); throw ex; } if( logger.isInfoEnabled() ) { String contextPathInfo = contextPath; if( contextPathInfo == null ) { contextPathInfo = "--not specified: calculated via Javascript--"; } logger.info( "Servlet GLOBAL configuration: " + GlobalParameters.DEBUG + "=" + isDebug + ", " + GlobalParameters.PROVIDERS_URL + "=" + providersUrl + ", " + GlobalParameters.MINIFY + "=" + minifyEnabled + ", " + GlobalParameters.BATCH_REQUESTS_MULTITHREADING_ENABLED + "=" + isBatchRequestsMultithreadingEnabled + ", " + GlobalParameters.BATCH_REQUESTS_MIN_THREADS_POOOL_SIZE + "=" + batchRequestsMinThreadsPoolSize + ", " + GlobalParameters.BATCH_REQUESTS_MAX_THREADS_POOOL_SIZE + "=" + batchRequestsMaxThreadsPoolSize + ", " + GlobalParameters.BATCH_REQUESTS_MAX_THREADS_PER_REQUEST + "=" + batchRequestsMaxThreadsPerRequest + ", " + GlobalParameters.BATCH_REQUESTS_THREAD_KEEP_ALIVE_SECONDS + "=" + batchRequestsThreadKeepAliveSeconds + ", " + GlobalParameters.GSON_BUILDER_CONFIGURATOR_CLASS + "=" + gsonConfiguratorClassName + ", " + GlobalParameters.DISPATCHER_CLASS + "=" + dispatcherClassName + ", " + GlobalParameters.JSON_REQUEST_PROCESSOR_THREAD_CLASS + "=" + jsonRequestProcessorThreadClassName + ", " + GlobalParameters.CONTEXT_PATH + "=" + contextPathInfo + ", " + GlobalParameters.CREATE_SOURCE_FILES + "=" + createSourceFiles ); } Class<? extends GsonBuilderConfigurator> gsonConfiguratorClass = getGsonBuilderConfiguratorClass(gsonConfiguratorClassName); Class<? extends Dispatcher> dispatcherClass = getDispatcherClass(dispatcherClassName); Class<? extends JsonRequestProcessorThread> jsonRequestProcessorClass = getJsonRequestProcessorThreadClass(jsonRequestProcessorThreadClassName); GlobalConfiguration result = new GlobalConfiguration( contextPath, providersUrl, isDebug, gsonConfiguratorClass, jsonRequestProcessorClass, dispatcherClass, minifyEnabled, isBatchRequestsMultithreadingEnabled, batchRequestsMinThreadsPoolSize, batchRequestsMaxThreadsPoolSize, batchRequestsThreadKeepAliveSeconds, batchRequestsMaxThreadsPerRequest, createSourceFiles); return result; } private static Class<? extends JsonRequestProcessorThread> getDefaultJsonRequestProcessoThreadClass() { return SsmJsonRequestProcessorThread.class; } private static Class<? extends Dispatcher> getDefaultDispatcherClass() { return SsmDispatcher.class; } @SuppressWarnings("unchecked") private static Class<? extends GsonBuilderConfigurator> getGsonBuilderConfiguratorClass(String gsonConfiguratorClassName) { assert !StringUtils.isEmpty(gsonConfiguratorClassName); Class<? extends GsonBuilderConfigurator> configuratorClass; try { configuratorClass = (Class<GsonBuilderConfigurator>)Class.forName(gsonConfiguratorClassName); if( !GsonBuilderConfigurator.class.isAssignableFrom(configuratorClass)) { ServletConfigurationException ex = ServletConfigurationException.forGsonBuilderConfiguratorMustImplementGsonBuilderConfiguratorInterface(gsonConfiguratorClassName ); logger.fatal( ex.getMessage(), ex ); throw ex; } return configuratorClass; } catch( ClassNotFoundException ex ) { ServletConfigurationException e = ServletConfigurationException.forClassNotFound(gsonConfiguratorClassName, ex ); logger.fatal( e.getMessage(), e ); throw e; } } @SuppressWarnings("unchecked") private static Class<? extends Dispatcher> getDispatcherClass(String dispatcherClassName) { assert !StringUtils.isEmpty(dispatcherClassName); Class<? extends Dispatcher> configuratorClass; try { configuratorClass = (Class<Dispatcher>)Class.forName(dispatcherClassName); if( !Dispatcher.class.isAssignableFrom(configuratorClass)) { ServletConfigurationException ex = ServletConfigurationException.forDispatcherMustImplementDispatcherInterface(dispatcherClassName ); logger.fatal( ex.getMessage(), ex ); throw ex; } return configuratorClass; } catch( ClassNotFoundException ex ) { ServletConfigurationException e = ServletConfigurationException.forClassNotFound(dispatcherClassName, ex ); logger.fatal( e.getMessage(), e ); throw e; } } @SuppressWarnings("unchecked") private static Class<? extends JsonRequestProcessorThread> getJsonRequestProcessorThreadClass(String jsonRequestProcessorThreadClassName) { assert !StringUtils.isEmpty(jsonRequestProcessorThreadClassName); Class<? extends JsonRequestProcessorThread> cls; try { cls = (Class<JsonRequestProcessorThread>)Class.forName(jsonRequestProcessorThreadClassName); if( !JsonRequestProcessorThread.class.isAssignableFrom(cls)) { ServletConfigurationException ex = ServletConfigurationException.forJsonRequestProcessorThreadMustImplementJsonRequestProcessorThreadInterface(jsonRequestProcessorThreadClassName ); logger.fatal( ex.getMessage(), ex ); throw ex; } return cls; } catch( ClassNotFoundException ex ) { ServletConfigurationException e = ServletConfigurationException.forClassNotFound(jsonRequestProcessorThreadClassName, ex ); logger.fatal( e.getMessage(), e ); throw e; } } @SuppressWarnings("unchecked") @CheckForNull private static Class<? extends ServletRegistryConfigurator> getRegistryConfiguratorClass(String registryConfiguratorClassName) { if( StringUtils.isEmpty(registryConfiguratorClassName)) { return null; } Class<? extends ServletRegistryConfigurator> configuratorClass; try { configuratorClass = (Class<ServletRegistryConfigurator>)Class.forName(registryConfiguratorClassName); if( !ServletRegistryConfigurator.class.isAssignableFrom(configuratorClass)) { ServletConfigurationException ex = ServletConfigurationException.forRegistryConfiguratorMustImplementGsonBuilderConfiguratorInterface(registryConfiguratorClassName ); logger.fatal( ex.getMessage(), ex ); throw ex; } return configuratorClass; } catch( ClassNotFoundException ex ) { ServletConfigurationException e = ServletConfigurationException.forClassNotFound(registryConfiguratorClassName, ex ); logger.fatal( e.getMessage(), e ); throw e; } } protected List<ApiConfiguration> createApiConfigurationsFromServletConfigurationApi(ServletConfig configuration) { assert configuration != null; List<ApiConfiguration> result = new ArrayList<ApiConfiguration>(); String apisParameter = ServletUtils.getRequiredParameter(configuration, GlobalParameters.APIS_PARAMETER); List<String> apis = StringUtils.getNonBlankValues(apisParameter, VALUES_SEPARATOR); logger.info( "Servlet APIs configuration: " + GlobalParameters.APIS_PARAMETER + "=" + apisParameter ); for( String api : apis) { ApiConfiguration apiConfiguration = createApiConfigurationFromServletConfigurationApi( configuration, api ); result.add( apiConfiguration ); } if( result.isEmpty() ) { logger.warn( "No apis specified"); } return result; } private ApiConfiguration createApiConfigurationFromServletConfigurationApi(ServletConfig configuration, String api) { assert configuration != null; assert !StringUtils.isEmpty(api); String apiFile = ServletUtils.getParameter( configuration, api + "." + ApiParameters.API_FILE, api + ApiConfiguration.DEFAULT_API_FILE_SUFFIX ); String fullGeneratedApiFile = getServletContext().getRealPath(apiFile); String apiNamespace = ServletUtils.getParameter( configuration, api + "." + ApiParameters.API_NAMESPACE, "" ); assert apiNamespace != null; String actionsNamespace = ServletUtils.getParameter( configuration, api + "." + ApiParameters.ACTIONS_NAMESPACE, "" ); // If apiNamespace is empty, try to use actionsNamespace: if still empty, use the api name itself if( apiNamespace.equals("")) { if( actionsNamespace.equals("")) { apiNamespace = ApiConfiguration.DEFAULT_NAMESPACE_PREFIX + api; if( logger.isDebugEnabled() ) { logger.debug( "Using the api name, prefixed with '" + ApiConfiguration.DEFAULT_NAMESPACE_PREFIX + "' as the value for " + ApiParameters.API_NAMESPACE); } } else { apiNamespace = actionsNamespace; logger.debug( "Using " + ApiParameters.ACTIONS_NAMESPACE + " as the value for " + ApiParameters.API_NAMESPACE); } } String classNames = ServletUtils.getParameter( configuration, api + "." + ApiParameters.CLASSES, "" ); List<Class<?>> classes = getClasses( classNames ); if( logger.isInfoEnabled() ) { logger.info( "Servlet '" + api + "' Api configuration: " + ApiParameters.API_NAMESPACE + "=" + apiNamespace + ", " + ApiParameters.ACTIONS_NAMESPACE + "=" + actionsNamespace + ", " + ApiParameters.API_FILE + "=" + apiFile + " => Full api file: " + fullGeneratedApiFile + ", " + ApiParameters.CLASSES + "=" + classNames); } if( classes.isEmpty() ) { logger.warn( "There are no action classes to register for api '" + api + "'"); } ApiConfiguration apiConfiguration = new ApiConfiguration( api, apiFile, fullGeneratedApiFile, apiNamespace, actionsNamespace, classes ); return apiConfiguration; } private static List<Class<?>> getClasses( String classes ) { assert classes != null; List<Class<?>> result = new ArrayList<Class<?>>(); if( StringUtils.isEmpty(classes) ) { return result; } List<String> classNames = StringUtils.getNonBlankValues( classes, VALUES_SEPARATOR ); for( String className : classNames ) { try { Class<?> cls = Class.forName( className ); result.add( cls ); } catch( ClassNotFoundException ex ) { logger.fatal( ex.getMessage(), ex ); ServletConfigurationException e = ServletConfigurationException.forClassNotFound(className, ex ); throw e; } } return result; } private static RequestType getFromRequestContentType( HttpServletRequest request ) { assert request != null; String contentType = request.getContentType(); String pathInfo = request.getPathInfo(); if( !StringUtils.isEmpty(pathInfo) && pathInfo.startsWith( PollRequestProcessor.PATHINFO_POLL_PREFIX)) { return RequestType.POLL; } else if( StringUtils.startsWithCaseInsensitive( contentType, "application/json") ) { return RequestType.JSON; } else if( StringUtils.startsWithCaseInsensitive( contentType, "application/x-www-form-urlencoded") && request.getMethod().equalsIgnoreCase("post")) { return RequestType.FORM_SIMPLE_POST; } else if( ServletFileUpload.isMultipartContent(request)) { return RequestType.FORM_UPLOAD_POST; } else if( RequestRouter.isSourceRequest(pathInfo)) { return RequestType.SOURCE; } else { String requestInfo = ServletUtils.getDetailedRequestInformation(request); RequestException ex = RequestException.forRequestFormatNotRecognized(); logger.error( "Error during file uploader: " + ex.getMessage() + "\nAdditional request information: " + requestInfo, ex ); throw ex; } } @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { assert request != null; assert response != null; doPost(request, response); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { assert request != null; assert response != null; NDC.push( "rid=" + Long.toString(getUniqueRequestId()) ); try { Timer timer = new Timer(); try { attachThreadLocalData( request, response); try { if( logger.isTraceEnabled()) { String requestInfo = ServletUtils.getDetailedRequestInformation(request); logger.trace( "Request info: " + requestInfo); } String requestEncoding = request.getCharacterEncoding(); // If we don't know what the request encoding is, assume it to be UTF-8 if( StringUtils.isEmpty(requestEncoding)) { request.setCharacterEncoding(EncodingUtils.UTF8); } response.setCharacterEncoding(EncodingUtils.UTF8); RequestType type = getFromRequestContentType(request); processRequest(request, response, type); } finally { detachThreadLocalData(); } } finally { timer.stop(); timer.logDebugTimeInMilliseconds("Total servlet processing time"); } } finally { NDC.pop(); } } @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="SF_SWITCH_NO_DEFAULT", justification="Missing a 'default' branch is not a problem with enums, given the appropriate compiler settings") private void processRequest(HttpServletRequest request, HttpServletResponse response, RequestType type) throws IOException { final String JSON_CONTENT_TYPE = "application/json"; 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!) final String HTML_CONTENT_TYPE = "text/html"; RequestRouter processor = getProcessor(); switch( type ) { case FORM_SIMPLE_POST: response.setContentType(JSON_CONTENT_TYPE); processor.processSimpleFormPostRequest( request.getReader(), response.getWriter() ); break; case FORM_UPLOAD_POST: response.setContentType(HTML_CONTENT_TYPE); // MUST be "text/html" for uploads to work! processUploadFormPost(request, response); break; case JSON: response.setContentType(JSON_CONTENT_TYPE); processor.processJsonRequest( request.getReader(), response.getWriter() ); break; case POLL: response.setContentType(JSON_CONTENT_TYPE); processor.processPollRequest( request.getReader(), response.getWriter(), request.getPathInfo() ); break; case SOURCE: response.setContentType(JAVASCRIPT_CONTENT_TYPE); processor.processSourceRequest( request.getReader(), response.getWriter(), request.getPathInfo()); break; } } protected void attachThreadLocalData( HttpServletRequest request, HttpServletResponse response) { WebContextManager.initializeWebContextForCurrentThread(this, request, response); } protected void detachThreadLocalData() { WebContextManager.detachFromCurrentThread(); } private void processUploadFormPost(HttpServletRequest request, HttpServletResponse response) throws IOException { assert request != null; assert response != null; RequestRouter router = getProcessor(); UploadFormPostRequestProcessor processor = router.createUploadFromProcessor(); try { router.processUploadFormPostRequest( processor, getFileItems(request), response.getWriter() ); } catch( FileUploadException e ) { processor.handleFileUploadException( e ); } } @SuppressWarnings("unchecked") private List<FileItem> getFileItems(HttpServletRequest request) throws FileUploadException { assert request != null; ServletFileUpload uploader = getUploader(); return uploader.parseRequest(request); } }