/**
   * Sets up the handlers in the servlet chain. We setup a chain for every path + extension match
   * possibility. (i.e. if there a m path mappings and n extension mappings we have n*m chains).
   *
   * <p>If a chain consists of only the default servlet then we add it as an async handler, so that
   * resources can be served up directly without using blocking operations.
   *
   * <p>TODO: this logic is a bit convoluted at the moment, we should look at simplifying it
   *
   * @param servletContext
   * @param threadSetupAction
   * @param listeners
   */
  private ServletPathMatches setupServletChains(
      final ServletContextImpl servletContext,
      final CompositeThreadSetupAction threadSetupAction,
      final ApplicationListeners listeners) {
    final List<Lifecycle> lifecycles = new ArrayList<Lifecycle>();
    // create the default servlet
    ServletChain defaultHandler = null;
    ServletHandler defaultServlet = null;

    final Map<String, ManagedFilter> managedFilterMap = new LinkedHashMap<String, ManagedFilter>();
    final Map<String, ServletHandler> allServlets = new HashMap<String, ServletHandler>();
    final Map<String, ServletHandler> extensionServlets = new HashMap<String, ServletHandler>();
    final Map<String, ServletHandler> pathServlets = new HashMap<String, ServletHandler>();

    final Set<String> pathMatches = new HashSet<String>();
    final Set<String> extensionMatches = new HashSet<String>();

    DeploymentInfo deploymentInfo = deployment.getDeploymentInfo();
    for (Map.Entry<String, FilterInfo> entry : deploymentInfo.getFilters().entrySet()) {
      final ManagedFilter mf = new ManagedFilter(entry.getValue(), servletContext);
      managedFilterMap.put(entry.getValue().getName(), mf);
      lifecycles.add(mf);
    }

    for (FilterMappingInfo mapping : deploymentInfo.getFilterMappings()) {
      if (mapping.getMappingType() == FilterMappingInfo.MappingType.URL) {
        String path = mapping.getMapping();
        if (!path.startsWith("*.")) {
          pathMatches.add(path);
        } else {
          extensionMatches.add(path.substring(2));
        }
      }
    }

    for (Map.Entry<String, ServletInfo> entry : deploymentInfo.getServlets().entrySet()) {
      ServletInfo servlet = entry.getValue();
      final ManagedServlet managedServlet = new ManagedServlet(servlet, servletContext);
      lifecycles.add(managedServlet);
      final ServletHandler handler = new ServletHandler(managedServlet);
      allServlets.put(entry.getKey(), handler);
      for (String path : entry.getValue().getMappings()) {
        if (path.equals("/")) {
          // the default servlet
          pathMatches.add("/*");
          if (pathServlets.containsKey("/*")) {
            throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path);
          }
          defaultServlet = handler;
          defaultHandler = servletChain(handler, managedServlet);
        } else if (!path.startsWith("*.")) {
          pathMatches.add(path);
          if (pathServlets.containsKey(path)) {
            throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path);
          }
          pathServlets.put(path, handler);
        } else {
          String ext = path.substring(2);
          extensionMatches.add(ext);
          extensionServlets.put(ext, handler);
        }
      }
    }

    if (defaultServlet == null) {
      final DefaultServletConfig config =
          deploymentInfo.getDefaultServletConfig() == null
              ? new DefaultServletConfig()
              : deploymentInfo.getDefaultServletConfig();
      DefaultServlet defaultInstance =
          new DefaultServlet(deployment, config, deploymentInfo.getWelcomePages());
      final ManagedServlet managedDefaultServlet =
          new ManagedServlet(
              new ServletInfo(
                  "io.undertow.DefaultServlet",
                  DefaultServlet.class,
                  new ImmediateInstanceFactory<Servlet>(defaultInstance)),
              servletContext);
      lifecycles.add(managedDefaultServlet);
      pathMatches.add("/*");
      defaultServlet = new ServletHandler(managedDefaultServlet);
      defaultHandler = new ServletChain(defaultServlet, managedDefaultServlet);
    }

    final ServletPathMatches.Builder builder = ServletPathMatches.builder();

    for (final String path : pathMatches) {
      ServletHandler targetServlet = resolveServletForPath(path, pathServlets);

      final Map<DispatcherType, List<ManagedFilter>> noExtension =
          new HashMap<DispatcherType, List<ManagedFilter>>();
      final Map<String, Map<DispatcherType, List<ManagedFilter>>> extension =
          new HashMap<String, Map<DispatcherType, List<ManagedFilter>>>();
      for (String ext : extensionMatches) {
        extension.put(ext, new HashMap<DispatcherType, List<ManagedFilter>>());
      }

      for (final FilterMappingInfo filterMapping : deploymentInfo.getFilterMappings()) {
        ManagedFilter filter = managedFilterMap.get(filterMapping.getFilterName());
        if (filterMapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) {
          if (targetServlet != null) {
            if (filterMapping
                .getMapping()
                .equals(targetServlet.getManagedServlet().getServletInfo().getName())) {
              addToListMap(noExtension, filterMapping.getDispatcher(), filter);
              for (Map<DispatcherType, List<ManagedFilter>> l : extension.values()) {
                addToListMap(l, filterMapping.getDispatcher(), filter);
              }
            }
          }
        } else {
          if (filterMapping.getMapping().isEmpty()
              || !filterMapping.getMapping().startsWith("*.")) {
            if (isFilterApplicable(path, filterMapping.getMapping())) {
              addToListMap(noExtension, filterMapping.getDispatcher(), filter);
              for (Map<DispatcherType, List<ManagedFilter>> l : extension.values()) {
                addToListMap(l, filterMapping.getDispatcher(), filter);
              }
            }
          } else {
            addToListMap(
                extension.get(filterMapping.getMapping().substring(2)),
                filterMapping.getDispatcher(),
                filter);
          }
        }
      }

      final ServletChain initialHandler;
      if (noExtension.isEmpty()) {
        if (targetServlet != null) {
          initialHandler = servletChain(targetServlet, targetServlet.getManagedServlet());
        } else {
          initialHandler = defaultHandler;
        }
      } else {
        FilterHandler handler;
        if (targetServlet != null) {
          handler = new FilterHandler(noExtension, targetServlet);
        } else {
          handler = new FilterHandler(noExtension, defaultServlet);
        }
        initialHandler =
            servletChain(
                handler,
                targetServlet == null
                    ? defaultServlet.getManagedServlet()
                    : targetServlet.getManagedServlet());
      }

      if (path.endsWith("/*")) {
        String prefix = path.substring(0, path.length() - 2);
        builder.addPrefixMatch(prefix, initialHandler);

        for (Map.Entry<String, Map<DispatcherType, List<ManagedFilter>>> entry :
            extension.entrySet()) {
          ServletHandler pathServlet = targetServlet;
          if (pathServlet == null) {
            pathServlet = extensionServlets.get(entry.getKey());
          }
          if (pathServlet == null) {
            pathServlet = defaultServlet;
          }
          HttpHandler handler = pathServlet;
          if (!entry.getValue().isEmpty()) {
            handler = new FilterHandler(entry.getValue(), handler);
          }
          builder.addExtensionMatch(
              prefix, entry.getKey(), servletChain(handler, pathServlet.getManagedServlet()));
        }
      } else if (path.isEmpty()) {
        builder.addExactMatch("/", initialHandler);
      } else {
        builder.addExactMatch(path, initialHandler);
      }
    }

    // now setup name based mappings
    // these are used for name based dispatch
    for (Map.Entry<String, ServletHandler> entry : allServlets.entrySet()) {
      final Map<DispatcherType, List<ManagedFilter>> filters =
          new HashMap<DispatcherType, List<ManagedFilter>>();
      for (final FilterMappingInfo filterMapping : deploymentInfo.getFilterMappings()) {
        ManagedFilter filter = managedFilterMap.get(filterMapping.getFilterName());
        if (filterMapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) {
          if (filterMapping.getMapping().equals(entry.getKey())) {
            addToListMap(filters, filterMapping.getDispatcher(), filter);
          }
        }
      }
      if (filters.isEmpty()) {
        builder.addNameMatch(
            entry.getKey(), servletChain(entry.getValue(), entry.getValue().getManagedServlet()));
      } else {
        builder.addNameMatch(
            entry.getKey(),
            servletChain(
                new FilterHandler(filters, entry.getValue()),
                entry.getValue().getManagedServlet()));
      }
    }

    builder.setDefaultServlet(defaultHandler);

    deployment.addLifecycleObjects(lifecycles);
    return builder.build();
  }
  @Override
  public void deploy() {
    final DeploymentInfo deploymentInfo = originalDeployment.clone();

    if (deploymentInfo.getServletStackTraces() == ServletStackTraces.ALL) {
      UndertowServletLogger.REQUEST_LOGGER.servletStackTracesAll(
          deploymentInfo.getDeploymentName());
    }

    deploymentInfo.validate();
    final DeploymentImpl deployment = new DeploymentImpl(this, deploymentInfo, servletContainer);
    this.deployment = deployment;

    final List<ThreadSetupHandler> setup = new ArrayList<>();
    setup.add(ServletRequestContextThreadSetupAction.INSTANCE);
    setup.add(new ContextClassLoaderSetupAction(deploymentInfo.getClassLoader()));
    setup.addAll(deploymentInfo.getThreadSetupActions());
    deployment.setThreadSetupActions(setup);

    final ServletContextImpl servletContext = new ServletContextImpl(servletContainer, deployment);
    deployment.setServletContext(servletContext);
    handleExtensions(deploymentInfo, servletContext);
    deployment.getServletPaths().setWelcomePages(deploymentInfo.getWelcomePages());

    deployment.setDefaultCharset(Charset.forName(deploymentInfo.getDefaultEncoding()));

    handleDeploymentSessionConfig(deploymentInfo, servletContext);

    deployment.setSessionManager(
        deploymentInfo.getSessionManagerFactory().createSessionManager(deployment));
    deployment
        .getSessionManager()
        .setDefaultSessionTimeout(deploymentInfo.getDefaultSessionTimeout());

    try {
      deployment
          .createThreadSetupAction(
              new ThreadSetupHandler.Action<Void, Object>() {
                @Override
                public Void call(HttpServerExchange exchange, Object ignore) throws Exception {
                  final ApplicationListeners listeners = createListeners();
                  listeners.start();

                  deployment.setApplicationListeners(listeners);

                  // now create the servlets and filters that we know about. We can still get more
                  // later
                  createServletsAndFilters(deployment, deploymentInfo);

                  // first run the SCI's
                  for (final ServletContainerInitializerInfo sci :
                      deploymentInfo.getServletContainerInitializers()) {
                    final InstanceHandle<? extends ServletContainerInitializer> instance =
                        sci.getInstanceFactory().createInstance();
                    try {
                      instance.getInstance().onStartup(sci.getHandlesTypes(), servletContext);
                    } finally {
                      instance.release();
                    }
                  }

                  deployment
                      .getSessionManager()
                      .registerSessionListener(
                          new SessionListenerBridge(deployment, listeners, servletContext));
                  for (SessionListener listener : deploymentInfo.getSessionListeners()) {
                    deployment.getSessionManager().registerSessionListener(listener);
                  }

                  initializeErrorPages(deployment, deploymentInfo);
                  initializeMimeMappings(deployment, deploymentInfo);
                  initializeTempDir(servletContext, deploymentInfo);
                  listeners.contextInitialized();
                  // run

                  HttpHandler wrappedHandlers = ServletDispatchingHandler.INSTANCE;
                  wrappedHandlers =
                      wrapHandlers(wrappedHandlers, deploymentInfo.getInnerHandlerChainWrappers());
                  if (!deploymentInfo.isSecurityDisabled()) {
                    HttpHandler securityHandler = setupSecurityHandlers(wrappedHandlers);
                    wrappedHandlers =
                        new PredicateHandler(
                            DispatcherTypePredicate.REQUEST, securityHandler, wrappedHandlers);
                  }
                  HttpHandler outerHandlers =
                      wrapHandlers(wrappedHandlers, deploymentInfo.getOuterHandlerChainWrappers());
                  wrappedHandlers =
                      new PredicateHandler(
                          DispatcherTypePredicate.REQUEST, outerHandlers, wrappedHandlers);
                  wrappedHandlers =
                      handleDevelopmentModePersistentSessions(
                          wrappedHandlers,
                          deploymentInfo,
                          deployment.getSessionManager(),
                          servletContext);

                  MetricsCollector metrics = deploymentInfo.getMetricsCollector();
                  if (metrics != null) {
                    wrappedHandlers = new MetricsChainHandler(wrappedHandlers, metrics, deployment);
                  }
                  if (deploymentInfo.getCrawlerSessionManagerConfig() != null) {
                    wrappedHandlers =
                        new CrawlerSessionManagerHandler(
                            deploymentInfo.getCrawlerSessionManagerConfig(), wrappedHandlers);
                  }

                  final ServletInitialHandler servletInitialHandler =
                      SecurityActions.createServletInitialHandler(
                          deployment.getServletPaths(),
                          wrappedHandlers,
                          deployment,
                          servletContext);

                  HttpHandler initialHandler =
                      wrapHandlers(
                          servletInitialHandler,
                          deployment.getDeploymentInfo().getInitialHandlerChainWrappers());
                  initialHandler = new HttpContinueReadHandler(initialHandler);
                  if (deploymentInfo.getUrlEncoding() != null) {
                    initialHandler =
                        Handlers.urlDecodingHandler(
                            deploymentInfo.getUrlEncoding(), initialHandler);
                  }
                  deployment.setInitialHandler(initialHandler);
                  deployment.setServletHandler(servletInitialHandler);
                  deployment
                      .getServletPaths()
                      .invalidate(); // make sure we have a fresh set of servlet paths
                  servletContext.initDone();
                  return null;
                }
              })
          .call(null, null);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    state = State.DEPLOYED;
  }