All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.softwarementors.extjs.djn.servlet.DirectJNgineServlet Maven / Gradle / Ivy

/*
 * 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 .
 * 
 * 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.Cookie;
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  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 processors = new HashMap();
  @NonNull private static Map uploaders = new HashMap();
  
  // 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++;
  }

  private final boolean antiCsrfTokenEnabled;
  private final String antiCsrfTokenName;
  private final long maxUploadSize;

  public DirectJNgineServlet(final boolean antiCsrfTokenEnabled, final String antiCsrfTokenName, final long maxUploadSize) {
    this.antiCsrfTokenEnabled = antiCsrfTokenEnabled;
    this.antiCsrfTokenName = antiCsrfTokenName;
    this.maxUploadSize = maxUploadSize;
  }

  public DirectJNgineServlet() {
    this(false, null, Integer.MAX_VALUE);
  }

  @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 registryConfiguratorClass = getRegistryConfiguratorClass(registryConfiguratorClassName);
    List 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(this.maxUploadSize) );
    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 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 configuratorClass, Registry registry, ServletConfig config) {
    ServletRegistryConfigurator registryConfigurator = createCustomRegistryConfigurator( configuratorClass ); // registry.getGlobalConfiguration().getRegistryConfiguratorClass() ).configure( registry, configuration);
    registryConfigurator.configure(registry, config);
  }
  
  private static ServletRegistryConfigurator createCustomRegistryConfigurator( Class 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 gsonConfiguratorClass = getGsonBuilderConfiguratorClass(gsonConfiguratorClassName);
    Class dispatcherClass = getDispatcherClass(dispatcherClassName);
    Class 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 getDefaultJsonRequestProcessoThreadClass() {
    return SsmJsonRequestProcessorThread.class;
  }

  private static Class getDefaultDispatcherClass() {
    return SsmDispatcher.class;
  }

  @SuppressWarnings("unchecked")
  private static Class getGsonBuilderConfiguratorClass(String gsonConfiguratorClassName) {
    assert !StringUtils.isEmpty(gsonConfiguratorClassName);
    
    Class configuratorClass;
    try {
      configuratorClass = (Class)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 getDispatcherClass(String dispatcherClassName) {
    assert !StringUtils.isEmpty(dispatcherClassName);
    
    Class configuratorClass;
    try {
      configuratorClass = (Class)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 getJsonRequestProcessorThreadClass(String jsonRequestProcessorThreadClassName) {
    assert !StringUtils.isEmpty(jsonRequestProcessorThreadClassName);
    
    Class cls;
    try {
      cls = (Class)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 getRegistryConfiguratorClass(String registryConfiguratorClassName) {
    if( StringUtils.isEmpty(registryConfiguratorClassName)) {
      return null;
    }
    
    Class configuratorClass;
    try {
      configuratorClass = (Class)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 createApiConfigurationsFromServletConfigurationApi(ServletConfig configuration) {
    assert configuration != null;

    List result = new ArrayList();    
    String apisParameter = ServletUtils.getRequiredParameter(configuration, GlobalParameters.APIS_PARAMETER);
    List 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> 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> getClasses( String classes )  {
    assert classes != null;
    
    List> result = new ArrayList>();
    if( StringUtils.isEmpty(classes) ) {
      return result;
    }
    List 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 {
      final List fileItems = getFileItems(request);
      if (isValidAntiCsrfTokenMatching(request, fileItems)) {
        router.processUploadFormPostRequest(processor, fileItems, response.getWriter());
      } else {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Anti cross-site request forgery token mismatch");
      }
    }
    catch( FileUploadException e ) {
      processor.handleFileUploadException( e );
    }
  }

  @SuppressWarnings("unchecked")
  private List getFileItems(HttpServletRequest request) throws FileUploadException {
    assert request != null;

    ServletFileUpload uploader = getUploader();
    return uploader.parseRequest(request);
  }

  private boolean isValidAntiCsrfTokenMatching(final HttpServletRequest request, final List fileItems) {
    if (antiCsrfTokenEnabled) {
      String cookie = getAntiCsrfTokenCookie(request);
      String form = getAntiCsrfTokenField(fileItems);

      return cookie != null && cookie.equals(form);
    }

    return true;
  }

  private String getAntiCsrfTokenCookie(final HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
      for (Cookie cookie : cookies) {
        if (org.apache.commons.lang.StringUtils.equals(antiCsrfTokenName, cookie.getName())) {
          return cookie.getValue();
        }
      }
    }
    return null;
  }

  private String getAntiCsrfTokenField(final List fileItems) {
    for (FileItem item : fileItems) {
      if (item.isFormField() && org.apache.commons.lang.StringUtils.equals(antiCsrfTokenName, item.getFieldName())) {
        return item.getString();
      }
    }
    return null;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy