/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package io.undertow.servlet.api;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;

import javax.servlet.DispatcherType;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletContextListener;
import javax.servlet.descriptor.JspConfigDescriptor;

import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.AuthenticationMechanismFactory;
import io.undertow.security.api.AuthenticationMode;
import io.undertow.security.api.NotificationReceiver;
import io.undertow.security.api.SecurityContextFactory;
import io.undertow.security.idm.IdentityManager;
import io.undertow.server.HandlerWrapper;
import io.undertow.server.handlers.resource.ResourceManager;
import io.undertow.server.session.SecureRandomSessionIdGenerator;
import io.undertow.server.session.SessionIdGenerator;
import io.undertow.server.session.SessionListener;
import io.undertow.servlet.ServletExtension;
import io.undertow.servlet.UndertowServletMessages;
import io.undertow.servlet.core.DefaultAuthorizationManager;
import io.undertow.servlet.core.InMemorySessionManagerFactory;
import io.undertow.servlet.util.DefaultClassIntrospector;
import io.undertow.util.ImmediateAuthenticationMechanismFactory;

/**
 * Represents a servlet deployment.
 *
 * @author Stuart Douglas
 */
public class DeploymentInfo implements Cloneable {

    private String deploymentName;
    private String displayName;
    private String contextPath;
    private ClassLoader classLoader;
    private ResourceManager resourceManager = ResourceManager.EMPTY_RESOURCE_MANAGER;
    private ClassIntrospecter classIntrospecter = DefaultClassIntrospector.INSTANCE;
    private int majorVersion = 4;
    private int minorVersion = 0;
    private int containerMajorVersion = 4;
    private int containerMinorVersion = 0;
    private Executor executor;
    private Executor asyncExecutor;
    private Path tempDir;
    private JspConfigDescriptor jspConfigDescriptor;
    private DefaultServletConfig defaultServletConfig;
    private SessionManagerFactory sessionManagerFactory = new InMemorySessionManagerFactory();
    private LoginConfig loginConfig;
    private IdentityManager identityManager;
    private ConfidentialPortManager confidentialPortManager;
    private boolean allowNonStandardWrappers = false;
    private int defaultSessionTimeout = 60 * 30;
    private ConcurrentMap<String, Object> servletContextAttributeBackingMap;
    private ServletSessionConfig servletSessionConfig;
    private String hostName = "localhost";
    private boolean denyUncoveredHttpMethods = false;
    private ServletStackTraces servletStackTraces = ServletStackTraces.LOCAL_ONLY;
    private boolean invalidateSessionOnLogout = false;
    private int defaultCookieVersion = 0;
    private SessionPersistenceManager sessionPersistenceManager;
    private String defaultEncoding;
    private String defaultRequestEncoding;
    private String defaultResponseEncoding;
    private String urlEncoding = null;
    private boolean ignoreFlush = false;
    private AuthorizationManager authorizationManager = DefaultAuthorizationManager.INSTANCE;
    private AuthenticationMechanism jaspiAuthenticationMechanism;
    private SecurityContextFactory securityContextFactory;
    private String serverName = "Undertow";
    private MetricsCollector metricsCollector = null;
    private SessionConfigWrapper sessionConfigWrapper = null;
    private boolean eagerFilterInit = false;
    private boolean disableCachingForSecuredPages = true;
    private boolean escapeErrorMessage = true;
    private boolean sendCustomReasonPhraseOnError = false;
    private boolean useCachedAuthenticationMechanism = true;
    private boolean preservePathOnForward = true;
    private AuthenticationMode authenticationMode = AuthenticationMode.PRO_ACTIVE;
    private ExceptionHandler exceptionHandler;
    private final Map<String, ServletInfo> servlets = new HashMap<>();
    private final Map<String, FilterInfo> filters = new HashMap<>();
    private final List<FilterMappingInfo> filterServletNameMappings = new ArrayList<>();
    private final List<FilterMappingInfo> filterUrlMappings = new ArrayList<>();
    private final List<ListenerInfo> listeners = new ArrayList<>();
    private final List<ServletContainerInitializerInfo> servletContainerInitializers = new ArrayList<>();
    private final List<ThreadSetupHandler> threadSetupActions = new ArrayList<>();
    private final Map<String, String> initParameters = new HashMap<>();
    private final Map<String, Object> servletContextAttributes = new HashMap<>();
    private final Map<String, String> localeCharsetMapping = new HashMap<>();
    private final List<String> welcomePages = new ArrayList<>();
    private final List<ErrorPage> errorPages = new ArrayList<>();
    private final List<MimeMapping> mimeMappings = new ArrayList<>();
    private final List<SecurityConstraint> securityConstraints = new ArrayList<>();
    private final Set<String> securityRoles = new HashSet<>();
    private final List<NotificationReceiver> notificationReceivers = new ArrayList<>();
    private final Map<String, AuthenticationMechanismFactory> authenticationMechanisms = new HashMap<>();
    private final List<LifecycleInterceptor> lifecycleInterceptors = new ArrayList<>();
    private final List<SessionListener> sessionListeners = new ArrayList<>();

    /**
     * additional servlet extensions
     */
    private final List<ServletExtension> servletExtensions = new ArrayList<>();

    /**
     * map of additional roles that should be applied to the given principal.
     */
    private final Map<String, Set<String>> principalVersusRolesMap = new HashMap<>();

    /**
     * Wrappers that are applied before the servlet initial handler, and before any servlet related object have been
     * created. If a wrapper wants to bypass servlet entirely it should register itself here.
     */
    private final List<HandlerWrapper> initialHandlerChainWrappers = new ArrayList<>();

    /**
     * Handler chain wrappers that are applied outside all other handlers, including security but after the initial
     * servlet handler.
     */
    private final List<HandlerWrapper> outerHandlerChainWrappers = new ArrayList<>();

    /**
     * Handler chain wrappers that are applied just before the servlet request is dispatched. At this point the security
     * handlers have run, and any security information is attached to the request.
     */
    private final List<HandlerWrapper> innerHandlerChainWrappers = new ArrayList<>();

    /**
     * A handler chain wrapper to wrap the initial stages of the security handlers, if this is set it is assumed it
     * is taking over the responsibility of setting the {@link io.undertow.security.api.SecurityContext} that can handle authentication and the
     * remaining Undertow handlers specific to authentication will be skipped.
     */
    private HandlerWrapper initialSecurityWrapper = null;

    /**
     * Handler chain wrappers that are applied just before the authentication mechanism is called. Theses handlers are
     * always called, even if authentication is not required
     */
    private final List<HandlerWrapper> securityWrappers = new ArrayList<>();

    /**
     * Multipart config that will be applied to all servlets that do not have an explicit config
     */
    private MultipartConfigElement defaultMultipartConfig;

    /**
     * Cache of common content types, to prevent allocations when parsing the charset
     */
    private int contentTypeCacheSize = 100;

    private boolean changeSessionIdOnLogin = true;

    private SessionIdGenerator sessionIdGenerator = new SecureRandomSessionIdGenerator();

    /**
     * Config for the {@link io.undertow.servlet.handlers.CrawlerSessionManagerHandler}
     */
    private CrawlerSessionManagerConfig crawlerSessionManagerConfig;

    private boolean securityDisabled;

    private boolean checkOtherSessionManagers = true;

    private final List<ServletContextListener> deploymentCompleteListeners = new ArrayList<>();

    /**
     * A map of content encoding to file extension for pre compressed resource (e.g. gzip -> .gz)
     */
    private final Map<String, String> preCompressedResources = new HashMap<>();

    public void validate() {
        if (deploymentName == null) {
            throw UndertowServletMessages.MESSAGES.paramCannotBeNull("deploymentName");
        }
        if (contextPath == null) {
            throw UndertowServletMessages.MESSAGES.paramCannotBeNull("contextName");
        }
        if (classLoader == null) {
            throw UndertowServletMessages.MESSAGES.paramCannotBeNull("classLoader");
        }
        if (resourceManager == null) {
            throw UndertowServletMessages.MESSAGES.paramCannotBeNull("resourceManager");
        }
        if (classIntrospecter == null) {
            throw UndertowServletMessages.MESSAGES.paramCannotBeNull("classIntrospecter");
        }

        for (final ServletInfo servlet : this.servlets.values()) {
            servlet.validate();
        }
        for (final FilterInfo filter : this.filters.values()) {
            filter.validate();
        }
        for (FilterMappingInfo mapping : this.filterServletNameMappings) {
            if (!this.filters.containsKey(mapping.getFilterName())) {
                throw UndertowServletMessages.MESSAGES.filterNotFound(mapping.getFilterName(), mapping.getMappingType() + " - " + mapping.getMapping());
            }
        }
        for (FilterMappingInfo mapping : this.filterUrlMappings) {
            if (!this.filters.containsKey(mapping.getFilterName())) {
                throw UndertowServletMessages.MESSAGES.filterNotFound(mapping.getFilterName(), mapping.getMappingType() + " - " + mapping.getMapping());
            }
        }
    }

    public String getDeploymentName() {
        return deploymentName;
    }

    public DeploymentInfo setDeploymentName(final String deploymentName) {
        this.deploymentName = deploymentName;
        return this;
    }

    public String getDisplayName() {
        return displayName;
    }

    public DeploymentInfo setDisplayName(final String displayName) {
        this.displayName = displayName;
        return this;
    }

    public String getContextPath() {
        return contextPath;
    }

    public DeploymentInfo setContextPath(final String contextPath) {
        if(contextPath != null && contextPath.isEmpty()) {
            this.contextPath = "/"; //we represent the root context as / instead of "", but both work
        } else {
            this.contextPath = contextPath;
        }
        return this;
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public DeploymentInfo setClassLoader(final ClassLoader classLoader) {
        this.classLoader = classLoader;
        return this;
    }

    public ResourceManager getResourceManager() {
        return resourceManager;
    }

    public DeploymentInfo setResourceManager(final ResourceManager resourceManager) {
        this.resourceManager = resourceManager;
        return this;
    }

    public ClassIntrospecter getClassIntrospecter() {
        return classIntrospecter;
    }

    public DeploymentInfo setClassIntrospecter(final ClassIntrospecter classIntrospecter) {
        this.classIntrospecter = classIntrospecter;
        return this;
    }

    public boolean isAllowNonStandardWrappers() {
        return allowNonStandardWrappers;
    }

    public DeploymentInfo setAllowNonStandardWrappers(final boolean allowNonStandardWrappers) {
        this.allowNonStandardWrappers = allowNonStandardWrappers;
        return this;
    }

    public int getDefaultSessionTimeout() {
        return defaultSessionTimeout;
    }

    /**
     * @param defaultSessionTimeout The default session timeout, in seconds
     */
    public DeploymentInfo setDefaultSessionTimeout(final int defaultSessionTimeout) {
        this.defaultSessionTimeout = defaultSessionTimeout;
        return this;
    }

    public String getDefaultEncoding() {
        return defaultEncoding;
    }

    /**
     * Sets the default encoding that will be used for servlet responses
     *
     * @param defaultEncoding The default encoding
     */
    public DeploymentInfo setDefaultEncoding(String defaultEncoding) {
        this.defaultEncoding = defaultEncoding;
        return this;
    }

    public String getUrlEncoding() {
        return urlEncoding;
    }

    /**
     * Sets the URL encoding. This will only take effect if the {@link io.undertow.UndertowOptions#DECODE_URL}
     * parameter has been set to false. This allows multiple deployments in the same server to use a different URL encoding
     *
     * @param urlEncoding The encoding to use
     */
    public DeploymentInfo setUrlEncoding(String urlEncoding) {
        this.urlEncoding = urlEncoding;
        return this;
    }

    public DeploymentInfo addServlet(final ServletInfo servlet) {
        servlets.put(servlet.getName(), servlet);
        return this;
    }

    public DeploymentInfo addServlets(final ServletInfo... servlets) {
        for (final ServletInfo servlet : servlets) {
            addServlet(servlet);
        }
        return this;
    }

    public DeploymentInfo addServlets(final Collection<ServletInfo> servlets) {
        for (final ServletInfo servlet : servlets) {
            addServlet(servlet);
        }
        return this;
    }

    public Map<String, ServletInfo> getServlets() {
        return servlets;
    }


    public DeploymentInfo addFilter(final FilterInfo filter) {
        filters.put(filter.getName(), filter);
        return this;
    }

    public DeploymentInfo addFilters(final FilterInfo... filters) {
        for (final FilterInfo filter : filters) {
            addFilter(filter);
        }
        return this;
    }

    public DeploymentInfo addFilters(final Collection<FilterInfo> filters) {
        for (final FilterInfo filter : filters) {
            addFilter(filter);
        }
        return this;
    }

    public Map<String, FilterInfo> getFilters() {
        return filters;
    }

    public DeploymentInfo addFilterUrlMapping(final String filterName, final String mapping, DispatcherType dispatcher) {
        filterUrlMappings.add(new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.URL, mapping, dispatcher));
        return this;
    }

    public DeploymentInfo addFilterServletNameMapping(final String filterName, final String mapping, DispatcherType dispatcher) {
        filterServletNameMappings.add(new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.SERVLET, mapping, dispatcher));
        return this;
    }

    public DeploymentInfo insertFilterUrlMapping(final int pos, final String filterName, final String mapping, DispatcherType dispatcher) {
        filterUrlMappings.add(pos, new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.URL, mapping, dispatcher));
        return this;
    }

    public DeploymentInfo insertFilterServletNameMapping(final int pos, final String filterName, final String mapping, DispatcherType dispatcher) {
        filterServletNameMappings.add(pos, new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.SERVLET, mapping, dispatcher));
        return this;
    }

    public List<FilterMappingInfo> getFilterMappings() {
        final ArrayList<FilterMappingInfo> ret = new ArrayList<>(filterUrlMappings);
        ret.addAll(filterServletNameMappings);
        return ret;
    }


    public DeploymentInfo addListener(final ListenerInfo listener) {
        listeners.add(listener);
        return this;
    }

    public DeploymentInfo addListeners(final ListenerInfo... listeners) {
        this.listeners.addAll(Arrays.asList(listeners));
        return this;
    }

    public DeploymentInfo addListeners(final Collection<ListenerInfo> listeners) {
        this.listeners.addAll(listeners);
        return this;
    }

    public List<ListenerInfo> getListeners() {
        return listeners;
    }

    public int getMajorVersion() {
        return majorVersion;
    }

    public DeploymentInfo setMajorVersion(final int majorVersion) {
        this.majorVersion = majorVersion;
        return this;
    }

    public int getMinorVersion() {
        return minorVersion;
    }

    public DeploymentInfo setMinorVersion(final int minorVersion) {
        this.minorVersion = minorVersion;
        return this;
    }

    public DeploymentInfo addServletContainerInitializer(final ServletContainerInitializerInfo servletContainerInitializer) {
        servletContainerInitializers.add(servletContainerInitializer);
        return this;
    }

    @Deprecated // UNDERTOW-1375 Method name is misspelled
    public DeploymentInfo addServletContainerInitalizer(final ServletContainerInitializerInfo servletContainerInitializer) {
        return addServletContainerInitializer(servletContainerInitializer);
    }

    public DeploymentInfo addServletContainerInitializers(final ServletContainerInitializerInfo... servletContainerInitializer) {
        servletContainerInitializers.addAll(Arrays.asList(servletContainerInitializer));
        return this;
    }

    @Deprecated // UNDERTOW-1375 Method name is misspelled
    public DeploymentInfo addServletContainerInitalizers(final ServletContainerInitializerInfo... servletContainerInitializer) {
        return addServletContainerInitializers(servletContainerInitializer);
    }

    public DeploymentInfo addServletContainerInitializers(final List<ServletContainerInitializerInfo> servletContainerInitializer) {
        servletContainerInitializers.addAll(servletContainerInitializer);
        return this;
    }

    @Deprecated // UNDERTOW-1375 Method name is misspelled
    public DeploymentInfo addServletContainerInitalizers(final List<ServletContainerInitializerInfo> servletContainerInitializers) {
        return addServletContainerInitializers(servletContainerInitializers);
    }

    public List<ServletContainerInitializerInfo> getServletContainerInitializers() {
        return servletContainerInitializers;
    }

    @Deprecated
    public DeploymentInfo addThreadSetupAction(final ThreadSetupAction action) {
        threadSetupActions.add(new LegacyThreadSetupActionWrapper(action));
        return this;
    }

    public DeploymentInfo addThreadSetupAction(final ThreadSetupHandler action) {
        threadSetupActions.add(action);
        return this;
    }

    public List<ThreadSetupHandler> getThreadSetupActions() {
        return threadSetupActions;
    }

    public boolean isEagerFilterInit() {
        return eagerFilterInit;
    }

    public DeploymentInfo setEagerFilterInit(boolean eagerFilterInit) {
        this.eagerFilterInit = eagerFilterInit;
        return this;
    }

    public DeploymentInfo addInitParameter(final String name, final String value) {
        initParameters.put(name, value);
        return this;
    }

    public Map<String, String> getInitParameters() {
        return initParameters;
    }

    public DeploymentInfo addServletContextAttribute(final String name, final Object value) {
        servletContextAttributes.put(name, value);
        return this;
    }

    public Map<String, Object> getServletContextAttributes() {
        return servletContextAttributes;
    }

    public DeploymentInfo addWelcomePage(final String welcomePage) {
        this.welcomePages.add(welcomePage);
        return this;
    }

    public DeploymentInfo addWelcomePages(final String... welcomePages) {
        this.welcomePages.addAll(Arrays.asList(welcomePages));
        return this;
    }

    public DeploymentInfo addWelcomePages(final Collection<String> welcomePages) {
        this.welcomePages.addAll(welcomePages);
        return this;
    }

    public List<String> getWelcomePages() {
        return welcomePages;
    }

    public DeploymentInfo addErrorPage(final ErrorPage errorPage) {
        this.errorPages.add(errorPage);
        return this;
    }

    public DeploymentInfo addErrorPages(final ErrorPage... errorPages) {
        this.errorPages.addAll(Arrays.asList(errorPages));
        return this;
    }

    public DeploymentInfo addErrorPages(final Collection<ErrorPage> errorPages) {
        this.errorPages.addAll(errorPages);
        return this;
    }

    public List<ErrorPage> getErrorPages() {
        return errorPages;
    }

    public DeploymentInfo addMimeMapping(final MimeMapping mimeMappings) {
        this.mimeMappings.add(mimeMappings);
        return this;
    }

    public DeploymentInfo addMimeMappings(final MimeMapping... mimeMappings) {
        this.mimeMappings.addAll(Arrays.asList(mimeMappings));
        return this;
    }

    public DeploymentInfo addMimeMappings(final Collection<MimeMapping> mimeMappings) {
        this.mimeMappings.addAll(mimeMappings);
        return this;
    }

    public List<MimeMapping> getMimeMappings() {
        return mimeMappings;
    }


    public DeploymentInfo addSecurityConstraint(final SecurityConstraint securityConstraint) {
        this.securityConstraints.add(securityConstraint);
        return this;
    }

    public DeploymentInfo addSecurityConstraints(final SecurityConstraint... securityConstraints) {
        this.securityConstraints.addAll(Arrays.asList(securityConstraints));
        return this;
    }

    public DeploymentInfo addSecurityConstraints(final Collection<SecurityConstraint> securityConstraints) {
        this.securityConstraints.addAll(securityConstraints);
        return this;
    }

    public List<SecurityConstraint> getSecurityConstraints() {
        return securityConstraints;
    }

    public Executor getExecutor() {
        return executor;
    }

    /**
     * Sets the executor that will be used to run servlet invocations. If this is null then the XNIO worker pool will be
     * used.
     * <p>
     * Individual servlets may use a different executor
     * <p>
     * If this is null then the current executor is used, which is generally the XNIO worker pool
     *
     * @param executor The executor
     * @see ServletInfo#executor
     */
    public DeploymentInfo setExecutor(final Executor executor) {
        this.executor = executor;
        return this;
    }

    public Executor getAsyncExecutor() {
        return asyncExecutor;
    }

    /**
     * Sets the executor that is used to run async tasks.
     * <p>
     * If this is null then {@link #executor} is used, if this is also null then the default is used
     *
     * @param asyncExecutor The executor
     */
    public DeploymentInfo setAsyncExecutor(final Executor asyncExecutor) {
        this.asyncExecutor = asyncExecutor;
        return this;
    }

    public File getTempDir() {
        if(tempDir == null) {
            return null;
        }
        return tempDir.toFile();
    }

    public Path getTempPath() {
        return tempDir;
    }

    /**
     * @return Returns the {@link #getTempDir() temp directory path} if it's
     * not null, else returns the system level temporary directory path
     * pointed to by the Java system property {@code java.io.tmpdir}
     */
    public Path requireTempPath() {
        if (tempDir != null) {
            return tempDir;
        }
        return Paths.get(SecurityActions.getSystemProperty("java.io.tmpdir"));
    }

    public DeploymentInfo setTempDir(final File tempDir) {
        this.tempDir = tempDir != null ? tempDir.toPath() : null;
        return this;
    }

    public DeploymentInfo setTempDir(final Path tempDir) {
        this.tempDir = tempDir;
        return this;
    }

    public boolean isIgnoreFlush() {
        return ignoreFlush;
    }

    public DeploymentInfo setIgnoreFlush(boolean ignoreFlush) {
        this.ignoreFlush = ignoreFlush;
        return this;
    }

    public JspConfigDescriptor getJspConfigDescriptor() {
        return jspConfigDescriptor;
    }

    public DeploymentInfo setJspConfigDescriptor(JspConfigDescriptor jspConfigDescriptor) {
        this.jspConfigDescriptor = jspConfigDescriptor;
        return this;
    }

    public DefaultServletConfig getDefaultServletConfig() {
        return defaultServletConfig;
    }

    public DeploymentInfo setDefaultServletConfig(final DefaultServletConfig defaultServletConfig) {
        this.defaultServletConfig = defaultServletConfig;
        return this;
    }

    public DeploymentInfo addLocaleCharsetMapping(final String locale, final String charset) {
        localeCharsetMapping.put(locale, charset);
        return this;
    }

    public Map<String, String> getLocaleCharsetMapping() {
        return localeCharsetMapping;
    }

    public SessionManagerFactory getSessionManagerFactory() {
        return sessionManagerFactory;
    }

    public DeploymentInfo setSessionManagerFactory(final SessionManagerFactory sessionManagerFactory) {
        this.sessionManagerFactory = sessionManagerFactory;
        return this;
    }

    public LoginConfig getLoginConfig() {
        return loginConfig;
    }

    public DeploymentInfo setLoginConfig(LoginConfig loginConfig) {
        this.loginConfig = loginConfig;
        return this;
    }

    public IdentityManager getIdentityManager() {
        return identityManager;
    }

    public DeploymentInfo setIdentityManager(IdentityManager identityManager) {
        this.identityManager = identityManager;
        return this;
    }

    public ConfidentialPortManager getConfidentialPortManager() {
        return confidentialPortManager;
    }

    public DeploymentInfo setConfidentialPortManager(ConfidentialPortManager confidentialPortManager) {
        this.confidentialPortManager = confidentialPortManager;
        return this;
    }

    public DeploymentInfo addSecurityRole(final String role) {
        this.securityRoles.add(role);
        return this;
    }

    public DeploymentInfo addSecurityRoles(final String... roles) {
        this.securityRoles.addAll(Arrays.asList(roles));
        return this;
    }

    public DeploymentInfo addSecurityRoles(final Collection<String> roles) {
        this.securityRoles.addAll(roles);
        return this;
    }

    public Set<String> getSecurityRoles() {
        return securityRoles;
    }

    /**
     * Adds an outer handler wrapper. This handler will be run after the servlet initial handler,
     * but before any other handlers. These are only run on REQUEST invocations, they
     * are not invoked on a FORWARD or INCLUDE.
     *
     * @param wrapper The wrapper
     */
    public DeploymentInfo addOuterHandlerChainWrapper(final HandlerWrapper wrapper) {
        outerHandlerChainWrappers.add(wrapper);
        return this;
    }

    public List<HandlerWrapper> getOuterHandlerChainWrappers() {
        return outerHandlerChainWrappers;
    }

    /**
     * Adds an inner handler chain wrapper. This handler will be run after the security handler,
     * but before any other servlet handlers, and will be run for every request
     *
     * @param wrapper The wrapper
     */
    public DeploymentInfo addInnerHandlerChainWrapper(final HandlerWrapper wrapper) {
        innerHandlerChainWrappers.add(wrapper);
        return this;
    }

    public List<HandlerWrapper> getInnerHandlerChainWrappers() {
        return innerHandlerChainWrappers;
    }

    public DeploymentInfo addInitialHandlerChainWrapper(final HandlerWrapper wrapper) {
        initialHandlerChainWrappers.add(wrapper);
        return this;
    }

    public List<HandlerWrapper> getInitialHandlerChainWrappers() {
        return initialHandlerChainWrappers;
    }


    /**
     * Sets the initial handler wrapper that will take over responsibility for establishing
     * a security context that will handle authentication for the request.
     *
     * Undertow specific authentication mechanisms will not be installed but Undertow handlers will
     * still make the decision as to if authentication is required and will subsequently
     * call {@link io.undertow.security.api.SecurityContext#authenticate()} as required.
     *
     * @param wrapper the {@link HandlerWrapper} to handle the initial security context installation.
     * @return {@code this} to allow chaining.
     */
    public DeploymentInfo setInitialSecurityWrapper(final HandlerWrapper wrapper) {
        this.initialSecurityWrapper = wrapper;

        return this;
    }

    public HandlerWrapper getInitialSecurityWrapper() {
        return initialSecurityWrapper;
    }

    /**
     * Adds a security handler. These are invoked before the authentication mechanism, and are always invoked
     * even if authentication is not required.
     * @param wrapper
     * @return
     */
    public DeploymentInfo addSecurityWrapper(final HandlerWrapper wrapper) {
        securityWrappers.add(wrapper);
        return this;
    }

    public List<HandlerWrapper> getSecurityWrappers() {
        return securityWrappers;
    }

    public DeploymentInfo addNotificationReceiver(final NotificationReceiver notificationReceiver) {
        this.notificationReceivers.add(notificationReceiver);
        return this;
    }

    public DeploymentInfo addNotificactionReceivers(final NotificationReceiver... notificationReceivers) {
        this.notificationReceivers.addAll(Arrays.asList(notificationReceivers));
        return this;
    }

    public DeploymentInfo addNotificationReceivers(final Collection<NotificationReceiver> notificationReceivers) {
        this.notificationReceivers.addAll(notificationReceivers);
        return this;
    }

    public List<NotificationReceiver> getNotificationReceivers() {
        return notificationReceivers;
    }

    public ConcurrentMap<String, Object> getServletContextAttributeBackingMap() {
        return servletContextAttributeBackingMap;
    }

    /**
     * Sets the map that will be used by the ServletContext implementation to store attributes.
     * <p>
     * This should usuablly be null, in which case Undertow will create a new map. This is only
     * used in situations where you want multiple deployments to share the same servlet context
     * attributes.
     *
     * @param servletContextAttributeBackingMap
     *         The backing map
     */
    public DeploymentInfo setServletContextAttributeBackingMap(final ConcurrentMap<String, Object> servletContextAttributeBackingMap) {
        this.servletContextAttributeBackingMap = servletContextAttributeBackingMap;
        return this;
    }

    public ServletSessionConfig getServletSessionConfig() {
        return servletSessionConfig;
    }

    public DeploymentInfo setServletSessionConfig(final ServletSessionConfig servletSessionConfig) {
        this.servletSessionConfig = servletSessionConfig;
        return this;
    }

    /**
     * @return the host name
     */
    public String getHostName() {
        return hostName;
    }

    public DeploymentInfo setHostName(final String hostName) {
        this.hostName = hostName;
        return this;
    }

    public boolean isDenyUncoveredHttpMethods() {
        return denyUncoveredHttpMethods;
    }

    public DeploymentInfo setDenyUncoveredHttpMethods(final boolean denyUncoveredHttpMethods) {
        this.denyUncoveredHttpMethods = denyUncoveredHttpMethods;
        return this;
    }

    public ServletStackTraces getServletStackTraces() {
        return servletStackTraces;
    }

    public DeploymentInfo setServletStackTraces(ServletStackTraces servletStackTraces) {
        this.servletStackTraces = servletStackTraces;
        return this;
    }

    public boolean isInvalidateSessionOnLogout() {
        return invalidateSessionOnLogout;
    }

    public DeploymentInfo setInvalidateSessionOnLogout(boolean invalidateSessionOnLogout) {
        this.invalidateSessionOnLogout = invalidateSessionOnLogout;
        return this;
    }

    public int getDefaultCookieVersion() {
        return defaultCookieVersion;
    }

    public DeploymentInfo setDefaultCookieVersion(int defaultCookieVersion) {
        this.defaultCookieVersion = defaultCookieVersion;
        return this;
    }

    public SessionPersistenceManager getSessionPersistenceManager() {
        return sessionPersistenceManager;
    }

    public DeploymentInfo setSessionPersistenceManager(SessionPersistenceManager sessionPersistenceManager) {
        this.sessionPersistenceManager = sessionPersistenceManager;
        return this;
    }

    public AuthorizationManager getAuthorizationManager() {
        return authorizationManager;
    }

    public DeploymentInfo setAuthorizationManager(AuthorizationManager authorizationManager) {
        this.authorizationManager = authorizationManager;
        return this;
    }

    public DeploymentInfo addPrincipalVsRoleMapping(final String principal, final String mapping) {
        Set<String> set = principalVersusRolesMap.get(principal);
        if (set == null) {
            principalVersusRolesMap.put(principal, set = new HashSet<>());
        }
        set.add(mapping);
        return this;
    }

    public DeploymentInfo addPrincipalVsRoleMappings(final String principal, final String... mappings) {
        Set<String> set = principalVersusRolesMap.get(principal);
        if (set == null) {
            principalVersusRolesMap.put(principal, set = new HashSet<>());
        }
        set.addAll(Arrays.asList(mappings));
        return this;
    }

    public DeploymentInfo addPrincipalVsRoleMappings(final String principal, final Collection<String> mappings) {
        Set<String> set = principalVersusRolesMap.get(principal);
        if (set == null) {
            principalVersusRolesMap.put(principal, set = new HashSet<>());
        }
        set.addAll(mappings);
        return this;
    }

    public Map<String, Set<String>> getPrincipalVersusRolesMap() {
        return principalVersusRolesMap;
    }

    /**
     * Removes all configured authentication mechanisms from the deployment.
     *
     * @return this deployment info
     */
    public DeploymentInfo clearLoginMethods() {
        if(loginConfig != null) {
            loginConfig.getAuthMethods().clear();
        }
        return this;
    }

    /**
     * Adds an authentication mechanism directly to the deployment. This mechanism will be first in the list.
     *
     * In general you should just use {@link #addAuthenticationMechanism(String, io.undertow.security.api.AuthenticationMechanismFactory)}
     * and allow the user to configure the methods they want by name.
     *
     * This method is essentially a convenience method, if is the same as registering a factory under the provided name that returns
     * and authentication mechanism, and then adding it to the login config list.
     *
     * If you want your mechanism to be the only one in the deployment you should first invoke {@link #clearLoginMethods()}.
     *
     * @param name The authentication mechanism name
     * @param mechanism The mechanism
     * @return this deployment info
     */
    public DeploymentInfo addFirstAuthenticationMechanism(final String name, final AuthenticationMechanism mechanism) {
        authenticationMechanisms.put(name, new ImmediateAuthenticationMechanismFactory(mechanism));
        if(loginConfig == null) {
            loginConfig = new LoginConfig(null);
        }
        loginConfig.addFirstAuthMethod(new AuthMethodConfig(name));
        return this;
    }

    /**
     * Adds an authentication mechanism directly to the deployment. This mechanism will be last in the list.
     *
     * In general you should just use {@link #addAuthenticationMechanism(String, io.undertow.security.api.AuthenticationMechanismFactory)}
     * and allow the user to configure the methods they want by name.
     *
     * This method is essentially a convenience method, if is the same as registering a factory under the provided name that returns
     * and authentication mechanism, and then adding it to the login config list.
     *
     * If you want your mechanism to be the only one in the deployment you should first invoke {@link #clearLoginMethods()}.
     *
     * @param name The authentication mechanism name
     * @param mechanism The mechanism
     * @return
     */
    public DeploymentInfo addLastAuthenticationMechanism(final String name, final AuthenticationMechanism mechanism) {
        authenticationMechanisms.put(name, new ImmediateAuthenticationMechanismFactory(mechanism));
        if(loginConfig == null) {
            loginConfig = new LoginConfig(null);
        }
        loginConfig.addLastAuthMethod(new AuthMethodConfig(name));
        return this;
    }

    /**
     * Adds an authentication mechanism. The name is case insenstive, and will be converted to uppercase internally.
     *
     * @param name    The name
     * @param factory The factory
     * @return
     */
    public DeploymentInfo addAuthenticationMechanism(final String name, final AuthenticationMechanismFactory factory) {
        authenticationMechanisms.put(name.toUpperCase(Locale.US), factory);
        return this;
    }

    public Map<String, AuthenticationMechanismFactory> getAuthenticationMechanisms() {
        return authenticationMechanisms;
    }

    /**
     * Returns true if the specified mechanism is present in the login config
     * @param mechanismName The mechanism name
     * @return true if the mechanism is enabled
     */
    public boolean isAuthenticationMechanismPresent(final String mechanismName) {
        if(loginConfig != null) {
            for(AuthMethodConfig method : loginConfig.getAuthMethods()) {
                if(method.getName().equalsIgnoreCase(mechanismName)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Adds an additional servlet extension to the deployment. Servlet extensions are generally discovered
     * using META-INF/services entries, however this may not be practical in all environments.
     * @param servletExtension The servlet extension
     * @return this
     */
    public DeploymentInfo addServletExtension(final ServletExtension servletExtension) {
        this.servletExtensions.add(servletExtension);
        return this;
    }

    public List<ServletExtension> getServletExtensions() {
        return servletExtensions;
    }

    public AuthenticationMechanism getJaspiAuthenticationMechanism() {
        return jaspiAuthenticationMechanism;
    }

    public DeploymentInfo setJaspiAuthenticationMechanism(AuthenticationMechanism jaspiAuthenticationMechanism) {
        this.jaspiAuthenticationMechanism = jaspiAuthenticationMechanism;
        return this;
    }

    public SecurityContextFactory getSecurityContextFactory() {
        return this.securityContextFactory;
    }

    public DeploymentInfo setSecurityContextFactory(final SecurityContextFactory securityContextFactory) {
        this.securityContextFactory = securityContextFactory;
        return this;
    }

    public String getServerName() {
        return serverName;
    }

    public DeploymentInfo setServerName(String serverName) {
        this.serverName = serverName;
        return this;
    }

    public DeploymentInfo setMetricsCollector(MetricsCollector metricsCollector){
        this.metricsCollector = metricsCollector;
        return this;
    }

    public MetricsCollector getMetricsCollector() {
        return metricsCollector;
    }

    public SessionConfigWrapper getSessionConfigWrapper() {
        return sessionConfigWrapper;
    }

    public DeploymentInfo setSessionConfigWrapper(SessionConfigWrapper sessionConfigWrapper) {
        this.sessionConfigWrapper = sessionConfigWrapper;
        return this;
    }

    public boolean isDisableCachingForSecuredPages() {
        return disableCachingForSecuredPages;
    }

    public DeploymentInfo setDisableCachingForSecuredPages(boolean disableCachingForSecuredPages) {
        this.disableCachingForSecuredPages = disableCachingForSecuredPages;
        return this;
    }

    public DeploymentInfo addLifecycleInterceptor(final LifecycleInterceptor interceptor) {
        lifecycleInterceptors.add(interceptor);
        return this;
    }

    public List<LifecycleInterceptor> getLifecycleInterceptors() {
        return lifecycleInterceptors;
    }

    /**
     * Returns the exception handler that is used by this deployment. By default this will simply
     * log unhandled exceptions
     */
    public ExceptionHandler getExceptionHandler() {
        return exceptionHandler;
    }

    /**
     * Sets the default exception handler for this deployment
     * @param exceptionHandler The exception handler
     * @return
     */
    public DeploymentInfo setExceptionHandler(ExceptionHandler exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
        return this;
    }

    public boolean isEscapeErrorMessage() {
        return escapeErrorMessage;
    }

    /**
     * Set if if the message passed to {@link javax.servlet.http.HttpServletResponse#sendError(int, String)} should be escaped.
     *
     * If this is false applications must be careful not to use user provided data (such as the URI) in the message
     *
     * @param escapeErrorMessage If the error message should be escaped
     */
    public DeploymentInfo setEscapeErrorMessage(boolean escapeErrorMessage) {
        this.escapeErrorMessage = escapeErrorMessage;
        return this;
    }


    public DeploymentInfo addSessionListener(SessionListener sessionListener) {
        this.sessionListeners.add(sessionListener);
        return this;
    }

    public List<SessionListener> getSessionListeners() {
        return sessionListeners;
    }

    public AuthenticationMode getAuthenticationMode() {
        return authenticationMode;
    }

    /**
     * Sets if this deployment should use pro-active authentication and always authenticate if the credentials are present
     * or constraint driven auth which will only call the authentication mechanisms for protected resources.
     *
     * Pro active auth means that requests for unprotected resources will still be associated with a user, which may be
     * useful for access logging.
     *
     *
     * @param authenticationMode The authentication mode to use
     * @return
     */
    public DeploymentInfo setAuthenticationMode(AuthenticationMode authenticationMode) {
        this.authenticationMode = authenticationMode;
        return this;
    }

    public MultipartConfigElement getDefaultMultipartConfig() {
        return defaultMultipartConfig;
    }

    public DeploymentInfo setDefaultMultipartConfig(MultipartConfigElement defaultMultipartConfig) {
        this.defaultMultipartConfig = defaultMultipartConfig;
        return this;
    }

    public int getContentTypeCacheSize() {
        return contentTypeCacheSize;
    }

    public DeploymentInfo setContentTypeCacheSize(int contentTypeCacheSize) {
        this.contentTypeCacheSize = contentTypeCacheSize;
        return this;
    }

    public SessionIdGenerator getSessionIdGenerator() {
        return sessionIdGenerator;
    }

    public DeploymentInfo setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
        this.sessionIdGenerator = sessionIdGenerator;
        return this;
    }


    public boolean isSendCustomReasonPhraseOnError() {
        return sendCustomReasonPhraseOnError;
    }

    public CrawlerSessionManagerConfig getCrawlerSessionManagerConfig() {
        return crawlerSessionManagerConfig;
    }

    public DeploymentInfo setCrawlerSessionManagerConfig(CrawlerSessionManagerConfig crawlerSessionManagerConfig) {
        this.crawlerSessionManagerConfig = crawlerSessionManagerConfig;
        return this;
    }

    /**
     * If this is true then the message parameter of {@link javax.servlet.http.HttpServletResponse#sendError(int, String)} and
     * {@link javax.servlet.http.HttpServletResponse#setStatus(int, String)} will be used as the HTTP reason phrase in
     * the response.
     *
     * @param sendCustomReasonPhraseOnError If the parameter to sendError should be used as a HTTP reason phrase
     * @return this
     */
    public DeploymentInfo setSendCustomReasonPhraseOnError(boolean sendCustomReasonPhraseOnError) {
        this.sendCustomReasonPhraseOnError = sendCustomReasonPhraseOnError;
        return this;
    }

    public boolean isChangeSessionIdOnLogin() {
        return changeSessionIdOnLogin;
    }

    public DeploymentInfo setChangeSessionIdOnLogin(boolean changeSessionIdOnLogin) {
        this.changeSessionIdOnLogin = changeSessionIdOnLogin;
        return this;
    }

    public boolean isUseCachedAuthenticationMechanism() {
        return useCachedAuthenticationMechanism;
    }

    /**
     * If this is set to false the the cached authenticated session mechanism won't be installed. If you want FORM and
     * other auth methods that require caching to work then you need to install another caching based auth method (such
     * as SSO).
     * @param useCachedAuthenticationMechanism If Undertow should use its internal authentication cache mechanism
     * @return this
     */
    public DeploymentInfo setUseCachedAuthenticationMechanism(boolean useCachedAuthenticationMechanism) {
        this.useCachedAuthenticationMechanism = useCachedAuthenticationMechanism;
        return this;
    }

    public boolean isSecurityDisabled() {
        return securityDisabled;
    }

    public DeploymentInfo setSecurityDisabled(boolean securityDisabled) {
        this.securityDisabled = securityDisabled;
        return this;
    }


    public boolean isCheckOtherSessionManagers() {
        return checkOtherSessionManagers;
    }

    /**
     * If this is true then when an existing invalid session id is found all other deployments in the container will have their
     * session managers checked to see if it represents a valid session. If it does then the session id will be re-used.
     */
    public DeploymentInfo setCheckOtherSessionManagers(boolean checkOtherSessionManagers) {
        this.checkOtherSessionManagers = checkOtherSessionManagers;
        return this;
    }

    public String getDefaultRequestEncoding() {
        return defaultRequestEncoding;
    }

    public DeploymentInfo setDefaultRequestEncoding(String defaultRequestEncoding) {
        this.defaultRequestEncoding = defaultRequestEncoding;
        return this;
    }

    public String getDefaultResponseEncoding() {
        return defaultResponseEncoding;
    }

    public DeploymentInfo setDefaultResponseEncoding(String defaultResponseEncoding) {
        this.defaultResponseEncoding = defaultResponseEncoding;
        return this;
    }

    /**
     * Adds a pre compressed resource encoding and maps it to a file extension
     *
     *
     * @param encoding The content encoding
     * @param extension The file extension
     * @return this builder
     */
    public DeploymentInfo addPreCompressedResourceEncoding(String encoding, String extension) {
       preCompressedResources.put(encoding, extension);
       return this;
    }

    public Map<String, String> getPreCompressedResources() {
        return preCompressedResources;
    }

    public int getContainerMajorVersion() {
        return containerMajorVersion;
    }

    public DeploymentInfo setContainerMajorVersion(int containerMajorVersion) {
        this.containerMajorVersion = containerMajorVersion;
        return this;
    }

    public int getContainerMinorVersion() {
        return containerMinorVersion;
    }

    public DeploymentInfo setContainerMinorVersion(int containerMinorVersion) {
        this.containerMinorVersion = containerMinorVersion;
        return this;
    }

    public boolean isPreservePathOnForward() {
        return preservePathOnForward;
    }

    public void setPreservePathOnForward(boolean preservePathOnForward) {
        this.preservePathOnForward = preservePathOnForward;
    }

    /**
     * Add's a listener that is only invoked once all other deployment steps have been completed
     *
     * The listeners <code>contextDestroyed</code> method will be called after all undeployment steps are undertaken
     *
     * @param servletContextListener
     * @return
     */
    public DeploymentInfo addDeploymentCompleteListener(ServletContextListener servletContextListener) {
        deploymentCompleteListeners.add(servletContextListener);
        return this;
    }

    public List<ServletContextListener> getDeploymentCompleteListeners() {
        return deploymentCompleteListeners;
    }

    @Override
    public DeploymentInfo clone() {
        final DeploymentInfo info = new DeploymentInfo()
                .setClassLoader(classLoader)
                .setContextPath(contextPath)
                .setResourceManager(resourceManager)
                .setMajorVersion(majorVersion)
                .setMinorVersion(minorVersion)
                .setDeploymentName(deploymentName)
                .setClassIntrospecter(classIntrospecter);

        for (Map.Entry<String, ServletInfo> e : servlets.entrySet()) {
            info.addServlet(e.getValue().clone());
        }

        for (Map.Entry<String, FilterInfo> e : filters.entrySet()) {
            info.addFilter(e.getValue().clone());
        }
        info.displayName = displayName;
        info.filterUrlMappings.addAll(filterUrlMappings);
        info.filterServletNameMappings.addAll(filterServletNameMappings);
        info.listeners.addAll(listeners);
        info.servletContainerInitializers.addAll(servletContainerInitializers);
        info.threadSetupActions.addAll(threadSetupActions);
        info.initParameters.putAll(initParameters);
        info.servletContextAttributes.putAll(servletContextAttributes);
        info.welcomePages.addAll(welcomePages);
        info.errorPages.addAll(errorPages);
        info.mimeMappings.addAll(mimeMappings);
        info.executor = executor;
        info.asyncExecutor = asyncExecutor;
        info.tempDir = tempDir;
        info.jspConfigDescriptor = jspConfigDescriptor;
        info.defaultServletConfig = defaultServletConfig;
        info.localeCharsetMapping.putAll(localeCharsetMapping);
        info.sessionManagerFactory = sessionManagerFactory;
        if (loginConfig != null) {
            info.loginConfig = loginConfig.clone();
        }
        info.identityManager = identityManager;
        info.confidentialPortManager = confidentialPortManager;
        info.defaultEncoding = defaultEncoding;
        info.urlEncoding = urlEncoding;
        info.securityConstraints.addAll(securityConstraints);
        info.outerHandlerChainWrappers.addAll(outerHandlerChainWrappers);
        info.innerHandlerChainWrappers.addAll(innerHandlerChainWrappers);
        info.initialSecurityWrapper = initialSecurityWrapper;
        info.securityWrappers.addAll(securityWrappers);
        info.initialHandlerChainWrappers.addAll(initialHandlerChainWrappers);
        info.securityRoles.addAll(securityRoles);
        info.notificationReceivers.addAll(notificationReceivers);
        info.allowNonStandardWrappers = allowNonStandardWrappers;
        info.defaultSessionTimeout = defaultSessionTimeout;
        info.servletContextAttributeBackingMap = servletContextAttributeBackingMap;
        info.servletSessionConfig = servletSessionConfig;
        info.hostName = hostName;
        info.denyUncoveredHttpMethods = denyUncoveredHttpMethods;
        info.servletStackTraces = servletStackTraces;
        info.invalidateSessionOnLogout = invalidateSessionOnLogout;
        info.defaultCookieVersion = defaultCookieVersion;
        info.sessionPersistenceManager = sessionPersistenceManager;
        for (Map.Entry<String, Set<String>> e : principalVersusRolesMap.entrySet()) {
            info.principalVersusRolesMap.put(e.getKey(), new HashSet<>(e.getValue()));
        }
        info.ignoreFlush = ignoreFlush;
        info.authorizationManager = authorizationManager;
        info.authenticationMechanisms.putAll(authenticationMechanisms);
        info.servletExtensions.addAll(servletExtensions);
        info.jaspiAuthenticationMechanism = jaspiAuthenticationMechanism;
        info.securityContextFactory = securityContextFactory;
        info.serverName = serverName;
        info.metricsCollector = metricsCollector;
        info.sessionConfigWrapper = sessionConfigWrapper;
        info.eagerFilterInit = eagerFilterInit;
        info.disableCachingForSecuredPages = disableCachingForSecuredPages;
        info.exceptionHandler = exceptionHandler;
        info.escapeErrorMessage = escapeErrorMessage;
        info.sessionListeners.addAll(sessionListeners);
        info.lifecycleInterceptors.addAll(lifecycleInterceptors);
        info.authenticationMode = authenticationMode;
        info.defaultMultipartConfig = defaultMultipartConfig;
        info.contentTypeCacheSize = contentTypeCacheSize;
        info.sessionIdGenerator = sessionIdGenerator;
        info.sendCustomReasonPhraseOnError = sendCustomReasonPhraseOnError;
        info.changeSessionIdOnLogin = changeSessionIdOnLogin;
        info.crawlerSessionManagerConfig = crawlerSessionManagerConfig;
        info.securityDisabled = securityDisabled;
        info.useCachedAuthenticationMechanism = useCachedAuthenticationMechanism;
        info.checkOtherSessionManagers = checkOtherSessionManagers;
        info.defaultRequestEncoding = defaultRequestEncoding;
        info.defaultResponseEncoding = defaultResponseEncoding;
        info.preCompressedResources.putAll(preCompressedResources);
        info.containerMajorVersion = containerMajorVersion;
        info.containerMinorVersion = containerMinorVersion;
        info.deploymentCompleteListeners.addAll(deploymentCompleteListeners);
        info.preservePathOnForward = preservePathOnForward;
        return info;
    }


}
