/*
 * Copyright 2002-2013 the original author or authors.
 *
 * 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 org.springframework.security.config.http;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

import org.w3c.dom.Element;

import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.config.Elements;
import org.springframework.security.config.http.GrantedAuthorityDefaultsParserUtils.AbstractGrantedAuthorityDefaultsBeanFactory;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
import org.springframework.security.web.access.channel.RetryWithHttpEntryPoint;
import org.springframework.security.web.access.channel.RetryWithHttpsEntryPoint;
import org.springframework.security.web.access.channel.SecureChannelProcessor;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.NullSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;

import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_FILTERS;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_HTTP_METHOD;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_PATH_PATTERN;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_REQUEST_MATCHER_REF;
import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_REQUIRES_CHANNEL;
import static org.springframework.security.config.http.SecurityFilters.CHANNEL_FILTER;
import static org.springframework.security.config.http.SecurityFilters.CONCURRENT_SESSION_FILTER;
import static org.springframework.security.config.http.SecurityFilters.CORS_FILTER;
import static org.springframework.security.config.http.SecurityFilters.CSRF_FILTER;
import static org.springframework.security.config.http.SecurityFilters.FILTER_SECURITY_INTERCEPTOR;
import static org.springframework.security.config.http.SecurityFilters.HEADERS_FILTER;
import static org.springframework.security.config.http.SecurityFilters.JAAS_API_SUPPORT_FILTER;
import static org.springframework.security.config.http.SecurityFilters.REQUEST_CACHE_FILTER;
import static org.springframework.security.config.http.SecurityFilters.SECURITY_CONTEXT_FILTER;
import static org.springframework.security.config.http.SecurityFilters.SERVLET_API_SUPPORT_FILTER;
import static org.springframework.security.config.http.SecurityFilters.SESSION_MANAGEMENT_FILTER;
import static org.springframework.security.config.http.SecurityFilters.WEB_ASYNC_MANAGER_FILTER;

/**
 * Stateful class which helps HttpSecurityBDP to create the configuration for the
 * &lt;http&gt; element.
 *
 * @author Luke Taylor
 * @author Rob Winch
 * @since 3.0
 */
class HttpConfigurationBuilder {
	private static final String ATT_CREATE_SESSION = "create-session";

	private static final String ATT_SESSION_FIXATION_PROTECTION = "session-fixation-protection";
	private static final String OPT_SESSION_FIXATION_NO_PROTECTION = "none";
	private static final String OPT_SESSION_FIXATION_MIGRATE_SESSION = "migrateSession";
	private static final String OPT_CHANGE_SESSION_ID = "changeSessionId";

	private static final String ATT_INVALID_SESSION_URL = "invalid-session-url";
	private static final String ATT_SESSION_AUTH_STRATEGY_REF = "session-authentication-strategy-ref";
	private static final String ATT_SESSION_AUTH_ERROR_URL = "session-authentication-error-url";
	private static final String ATT_SECURITY_CONTEXT_REPOSITORY = "security-context-repository-ref";
	private static final String ATT_INVALID_SESSION_STRATEGY_REF = "invalid-session-strategy-ref";
	private static final String ATT_DISABLE_URL_REWRITING = "disable-url-rewriting";

	private static final String ATT_ACCESS_MGR = "access-decision-manager-ref";
	private static final String ATT_ONCE_PER_REQUEST = "once-per-request";

	private static final String ATT_REF = "ref";

	private final Element httpElt;
	private final ParserContext pc;
	private final SessionCreationPolicy sessionPolicy;
	private final List<Element> interceptUrls;
	private final MatcherType matcherType;

	private BeanDefinition cpf;
	private BeanDefinition securityContextPersistenceFilter;
	private BeanReference contextRepoRef;
	private BeanReference sessionRegistryRef;
	private BeanDefinition concurrentSessionFilter;
	private BeanDefinition webAsyncManagerFilter;
	private BeanDefinition requestCacheAwareFilter;
	private BeanReference sessionStrategyRef;
	private RootBeanDefinition sfpf;
	private BeanDefinition servApiFilter;
	private BeanDefinition jaasApiFilter;
	private final BeanReference portMapper;
	private final BeanReference portResolver;
	private BeanReference fsi;
	private BeanReference requestCache;
	private BeanDefinition addHeadersFilter;
	private BeanMetadataElement corsFilter;
	private BeanDefinition csrfFilter;
	private BeanMetadataElement csrfLogoutHandler;
	private BeanMetadataElement csrfAuthStrategy;

	private CsrfBeanDefinitionParser csrfParser;

	private BeanDefinition invalidSession;
	private boolean addAllAuth;

	public HttpConfigurationBuilder(Element element, boolean addAllAuth,
			ParserContext pc, BeanReference portMapper, BeanReference portResolver,
			BeanReference authenticationManager) {
		this.httpElt = element;
		this.addAllAuth = addAllAuth;
		this.pc = pc;
		this.portMapper = portMapper;
		this.portResolver = portResolver;
		this.matcherType = MatcherType.fromElement(element);
		interceptUrls = DomUtils.getChildElementsByTagName(element,
				Elements.INTERCEPT_URL);

		for (Element urlElt : interceptUrls) {
			if (StringUtils.hasText(urlElt.getAttribute(ATT_FILTERS))) {
				pc.getReaderContext()
						.error("The use of \"filters='none'\" is no longer supported. Please define a"
								+ " separate <http> element for the pattern you want to exclude and use the attribute"
								+ " \"security='none'\".", pc.extractSource(urlElt));
			}
		}

		String createSession = element.getAttribute(ATT_CREATE_SESSION);

		if (StringUtils.hasText(createSession)) {
			sessionPolicy = createPolicy(createSession);
		}
		else {
			sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
		}

		createCsrfFilter();
		createSecurityContextPersistenceFilter();
		createSessionManagementFilters();
		createWebAsyncManagerFilter();
		createRequestCacheFilter();
		createServletApiFilter(authenticationManager);
		createJaasApiFilter();
		createChannelProcessingFilter();
		createFilterSecurityInterceptor(authenticationManager);
		createAddHeadersFilter();
		createCorsFilter();
	}

	private SessionCreationPolicy createPolicy(String createSession) {
		if ("ifRequired".equals(createSession)) {
			return SessionCreationPolicy.IF_REQUIRED;
		}
		else if ("always".equals(createSession)) {
			return SessionCreationPolicy.ALWAYS;
		}
		else if ("never".equals(createSession)) {
			return SessionCreationPolicy.NEVER;
		}
		else if ("stateless".equals(createSession)) {
			return SessionCreationPolicy.STATELESS;
		}

		throw new IllegalStateException("Cannot convert " + createSession + " to "
				+ SessionCreationPolicy.class.getName());
	}

	@SuppressWarnings("rawtypes")
	void setLogoutHandlers(ManagedList logoutHandlers) {
		if (logoutHandlers != null) {
			if (concurrentSessionFilter != null) {
				concurrentSessionFilter.getPropertyValues().add("logoutHandlers",
						logoutHandlers);
			}
			if (servApiFilter != null) {
				servApiFilter.getPropertyValues().add("logoutHandlers", logoutHandlers);
			}
		}
	}

	void setEntryPoint(BeanMetadataElement entryPoint) {
		if (servApiFilter != null) {
			servApiFilter.getPropertyValues().add("authenticationEntryPoint", entryPoint);
		}
	}

	void setAccessDeniedHandler(BeanMetadataElement accessDeniedHandler) {
		if (csrfParser != null) {
			csrfParser.initAccessDeniedHandler(this.invalidSession, accessDeniedHandler);
		}
	}

	// Needed to account for placeholders
	static String createPath(String path, boolean lowerCase) {
		return lowerCase ? path.toLowerCase() : path;
	}

	private void createSecurityContextPersistenceFilter() {
		BeanDefinitionBuilder scpf = BeanDefinitionBuilder
				.rootBeanDefinition(SecurityContextPersistenceFilter.class);

		String repoRef = httpElt.getAttribute(ATT_SECURITY_CONTEXT_REPOSITORY);
		String disableUrlRewriting = httpElt.getAttribute(ATT_DISABLE_URL_REWRITING);
		if(!StringUtils.hasText(disableUrlRewriting)) {
			disableUrlRewriting = "true";
		}

		if (StringUtils.hasText(repoRef)) {
			if (sessionPolicy == SessionCreationPolicy.ALWAYS) {
				scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE);
			}
		}
		else {
			BeanDefinitionBuilder contextRepo;
			if (sessionPolicy == SessionCreationPolicy.STATELESS) {
				contextRepo = BeanDefinitionBuilder
						.rootBeanDefinition(NullSecurityContextRepository.class);
			}
			else {
				contextRepo = BeanDefinitionBuilder
						.rootBeanDefinition(HttpSessionSecurityContextRepository.class);
				switch (sessionPolicy) {
				case ALWAYS:
					contextRepo.addPropertyValue("allowSessionCreation", Boolean.TRUE);
					scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE);
					break;
				case NEVER:
					contextRepo.addPropertyValue("allowSessionCreation", Boolean.FALSE);
					scpf.addPropertyValue("forceEagerSessionCreation", Boolean.FALSE);
					break;
				default:
					contextRepo.addPropertyValue("allowSessionCreation", Boolean.TRUE);
					scpf.addPropertyValue("forceEagerSessionCreation", Boolean.FALSE);
				}

				if ("true".equals(disableUrlRewriting)) {
					contextRepo.addPropertyValue("disableUrlRewriting", Boolean.TRUE);
				}
			}

			BeanDefinition repoBean = contextRepo.getBeanDefinition();
			repoRef = pc.getReaderContext().generateBeanName(repoBean);
			pc.registerBeanComponent(new BeanComponentDefinition(repoBean, repoRef));
		}

		contextRepoRef = new RuntimeBeanReference(repoRef);
		scpf.addConstructorArgValue(contextRepoRef);

		securityContextPersistenceFilter = scpf.getBeanDefinition();
	}

	private void createSessionManagementFilters() {
		Element sessionMgmtElt = DomUtils.getChildElementByTagName(httpElt,
				Elements.SESSION_MANAGEMENT);
		Element sessionCtrlElt = null;

		String sessionFixationAttribute = null;
		String invalidSessionUrl = null;
		String invalidSessionStrategyRef = null;
		String sessionAuthStratRef = null;
		String errorUrl = null;

		boolean sessionControlEnabled = false;
		if (sessionMgmtElt != null) {
			if (sessionPolicy == SessionCreationPolicy.STATELESS) {
				pc.getReaderContext().error(
						Elements.SESSION_MANAGEMENT + "  cannot be used"
								+ " in combination with " + ATT_CREATE_SESSION + "='"
								+ SessionCreationPolicy.STATELESS + "'",
						pc.extractSource(sessionMgmtElt));
			}
			sessionFixationAttribute = sessionMgmtElt
					.getAttribute(ATT_SESSION_FIXATION_PROTECTION);
			invalidSessionUrl = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_URL);
			invalidSessionStrategyRef = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_STRATEGY_REF);

			sessionAuthStratRef = sessionMgmtElt
					.getAttribute(ATT_SESSION_AUTH_STRATEGY_REF);
			errorUrl = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_ERROR_URL);
			sessionCtrlElt = DomUtils.getChildElementByTagName(sessionMgmtElt,
					Elements.CONCURRENT_SESSIONS);
			sessionControlEnabled = sessionCtrlElt != null;

			if (StringUtils.hasText(invalidSessionUrl) && StringUtils.hasText(invalidSessionStrategyRef)) {
				pc.getReaderContext().error(ATT_INVALID_SESSION_URL + " attribute cannot be used in combination with" +
						" the " + ATT_INVALID_SESSION_STRATEGY_REF + " attribute.", sessionMgmtElt);
			}

			if (sessionControlEnabled) {
				if (StringUtils.hasText(sessionAuthStratRef)) {
					pc.getReaderContext().error(
							ATT_SESSION_AUTH_STRATEGY_REF + " attribute cannot be used"
									+ " in combination with <"
									+ Elements.CONCURRENT_SESSIONS + ">",
							pc.extractSource(sessionCtrlElt));
				}
				createConcurrencyControlFilterAndSessionRegistry(sessionCtrlElt);
			}
		}

		if (!StringUtils.hasText(sessionFixationAttribute)) {
			Method changeSessionIdMethod = ReflectionUtils.findMethod(
					HttpServletRequest.class, "changeSessionId");
			sessionFixationAttribute = changeSessionIdMethod == null ? OPT_SESSION_FIXATION_MIGRATE_SESSION
					: OPT_CHANGE_SESSION_ID;
		}
		else if (StringUtils.hasText(sessionAuthStratRef)) {
			pc.getReaderContext().error(
					ATT_SESSION_FIXATION_PROTECTION + " attribute cannot be used"
							+ " in combination with " + ATT_SESSION_AUTH_STRATEGY_REF,
					pc.extractSource(sessionMgmtElt));
		}

		if (sessionPolicy == SessionCreationPolicy.STATELESS) {
			// SEC-1424: do nothing
			return;
		}

		boolean sessionFixationProtectionRequired = !sessionFixationAttribute
				.equals(OPT_SESSION_FIXATION_NO_PROTECTION);

		ManagedList<BeanMetadataElement> delegateSessionStrategies = new ManagedList<>();
		BeanDefinitionBuilder concurrentSessionStrategy;
		BeanDefinitionBuilder sessionFixationStrategy = null;
		BeanDefinitionBuilder registerSessionStrategy;

		if (csrfAuthStrategy != null) {
			delegateSessionStrategies.add(csrfAuthStrategy);
		}

		if (sessionControlEnabled) {
			assert sessionRegistryRef != null;
			concurrentSessionStrategy = BeanDefinitionBuilder
					.rootBeanDefinition(ConcurrentSessionControlAuthenticationStrategy.class);
			concurrentSessionStrategy.addConstructorArgValue(sessionRegistryRef);

			String maxSessions = sessionCtrlElt.getAttribute("max-sessions");

			if (StringUtils.hasText(maxSessions)) {
				concurrentSessionStrategy
						.addPropertyValue("maximumSessions", maxSessions);
			}

			String exceptionIfMaximumExceeded = sessionCtrlElt
					.getAttribute("error-if-maximum-exceeded");

			if (StringUtils.hasText(exceptionIfMaximumExceeded)) {
				concurrentSessionStrategy.addPropertyValue("exceptionIfMaximumExceeded",
						exceptionIfMaximumExceeded);
			}
			delegateSessionStrategies.add(concurrentSessionStrategy.getBeanDefinition());
		}
		boolean useChangeSessionId = OPT_CHANGE_SESSION_ID
				.equals(sessionFixationAttribute);
		if (sessionFixationProtectionRequired || StringUtils.hasText(invalidSessionUrl)) {
			if (useChangeSessionId) {
				sessionFixationStrategy = BeanDefinitionBuilder
						.rootBeanDefinition(ChangeSessionIdAuthenticationStrategy.class);
			}
			else {
				sessionFixationStrategy = BeanDefinitionBuilder
						.rootBeanDefinition(SessionFixationProtectionStrategy.class);
			}
			delegateSessionStrategies.add(sessionFixationStrategy.getBeanDefinition());
		}

		if (StringUtils.hasText(sessionAuthStratRef)) {
			delegateSessionStrategies.add(new RuntimeBeanReference(sessionAuthStratRef));
		}

		if (sessionControlEnabled) {
			registerSessionStrategy = BeanDefinitionBuilder
					.rootBeanDefinition(RegisterSessionAuthenticationStrategy.class);
			registerSessionStrategy.addConstructorArgValue(sessionRegistryRef);
			delegateSessionStrategies.add(registerSessionStrategy.getBeanDefinition());
		}

		if (delegateSessionStrategies.isEmpty()) {
			sfpf = null;
			return;
		}

		BeanDefinitionBuilder sessionMgmtFilter = BeanDefinitionBuilder
				.rootBeanDefinition(SessionManagementFilter.class);
		RootBeanDefinition failureHandler = new RootBeanDefinition(
				SimpleUrlAuthenticationFailureHandler.class);
		if (StringUtils.hasText(errorUrl)) {
			failureHandler.getPropertyValues().addPropertyValue("defaultFailureUrl",
					errorUrl);
		}
		sessionMgmtFilter
				.addPropertyValue("authenticationFailureHandler", failureHandler);
		sessionMgmtFilter.addConstructorArgValue(contextRepoRef);

		if (!StringUtils.hasText(sessionAuthStratRef) && sessionFixationStrategy != null
				&& !useChangeSessionId) {

			if (sessionFixationProtectionRequired) {
				sessionFixationStrategy.addPropertyValue("migrateSessionAttributes",
						Boolean.valueOf(sessionFixationAttribute
								.equals(OPT_SESSION_FIXATION_MIGRATE_SESSION)));
			}
		}

		if (!delegateSessionStrategies.isEmpty()) {
			BeanDefinitionBuilder sessionStrategy = BeanDefinitionBuilder
					.rootBeanDefinition(CompositeSessionAuthenticationStrategy.class);
			BeanDefinition strategyBean = sessionStrategy.getBeanDefinition();
			sessionStrategy.addConstructorArgValue(delegateSessionStrategies);
			sessionAuthStratRef = pc.getReaderContext().generateBeanName(strategyBean);
			pc.registerBeanComponent(new BeanComponentDefinition(strategyBean,
					sessionAuthStratRef));

		}



		if (StringUtils.hasText(invalidSessionUrl)) {
			BeanDefinitionBuilder invalidSessionBldr = BeanDefinitionBuilder
					.rootBeanDefinition(SimpleRedirectInvalidSessionStrategy.class);
			invalidSessionBldr.addConstructorArgValue(invalidSessionUrl);
			invalidSession = invalidSessionBldr.getBeanDefinition();
			sessionMgmtFilter.addPropertyValue("invalidSessionStrategy", invalidSession);
		} else if (StringUtils.hasText(invalidSessionStrategyRef)) {
			sessionMgmtFilter.addPropertyReference("invalidSessionStrategy", invalidSessionStrategyRef);
		}

		sessionMgmtFilter.addConstructorArgReference(sessionAuthStratRef);

		sfpf = (RootBeanDefinition) sessionMgmtFilter.getBeanDefinition();
		sessionStrategyRef = new RuntimeBeanReference(sessionAuthStratRef);
	}

	private void createConcurrencyControlFilterAndSessionRegistry(Element element) {
		final String ATT_EXPIRY_URL = "expired-url";
		final String ATT_EXPIRED_SESSION_STRATEGY_REF = "expired-session-strategy-ref";
		final String ATT_SESSION_REGISTRY_ALIAS = "session-registry-alias";
		final String ATT_SESSION_REGISTRY_REF = "session-registry-ref";

		CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(
				element.getTagName(), pc.extractSource(element));
		pc.pushContainingComponent(compositeDef);

		BeanDefinitionRegistry beanRegistry = pc.getRegistry();

		String sessionRegistryId = element.getAttribute(ATT_SESSION_REGISTRY_REF);

		if (!StringUtils.hasText(sessionRegistryId)) {
			// Register an internal SessionRegistryImpl if no external reference supplied.
			RootBeanDefinition sessionRegistry = new RootBeanDefinition(
					SessionRegistryImpl.class);
			sessionRegistryId = pc.getReaderContext().registerWithGeneratedName(
					sessionRegistry);
			pc.registerComponent(new BeanComponentDefinition(sessionRegistry,
					sessionRegistryId));
		}

		String registryAlias = element.getAttribute(ATT_SESSION_REGISTRY_ALIAS);
		if (StringUtils.hasText(registryAlias)) {
			beanRegistry.registerAlias(sessionRegistryId, registryAlias);
		}

		BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder
				.rootBeanDefinition(ConcurrentSessionFilter.class);
		filterBuilder.addConstructorArgReference(sessionRegistryId);

		Object source = pc.extractSource(element);
		filterBuilder.getRawBeanDefinition().setSource(source);
		filterBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

		String expiryUrl = element.getAttribute(ATT_EXPIRY_URL);
		String expiredSessionStrategyRef = element.getAttribute(ATT_EXPIRED_SESSION_STRATEGY_REF);

		if (StringUtils.hasText(expiryUrl) && StringUtils.hasText(expiredSessionStrategyRef)) {
			pc.getReaderContext().error("Cannot use 'expired-url' attribute and 'expired-session-strategy-ref'" +
					" attribute together.", source);
		}

		if (StringUtils.hasText(expiryUrl)) {
			BeanDefinitionBuilder expiredSessionBldr = BeanDefinitionBuilder
					.rootBeanDefinition(SimpleRedirectSessionInformationExpiredStrategy.class);
			expiredSessionBldr.addConstructorArgValue(expiryUrl);
			filterBuilder.addConstructorArgValue(expiredSessionBldr.getBeanDefinition());
		} else if (StringUtils.hasText(expiredSessionStrategyRef)) {
			filterBuilder.addConstructorArgReference(expiredSessionStrategyRef);
		}

		pc.popAndRegisterContainingComponent();

		concurrentSessionFilter = filterBuilder.getBeanDefinition();
		sessionRegistryRef = new RuntimeBeanReference(sessionRegistryId);
	}

	private void createWebAsyncManagerFilter() {
		boolean asyncSupported = ClassUtils.hasMethod(ServletRequest.class, "startAsync");
		if (asyncSupported) {
			webAsyncManagerFilter = new RootBeanDefinition(
					WebAsyncManagerIntegrationFilter.class);
		}
	}

	// Adds the servlet-api integration filter if required
	private void createServletApiFilter(BeanReference authenticationManager) {
		final String ATT_SERVLET_API_PROVISION = "servlet-api-provision";
		final String DEF_SERVLET_API_PROVISION = "true";

		String provideServletApi = httpElt.getAttribute(ATT_SERVLET_API_PROVISION);
		if (!StringUtils.hasText(provideServletApi)) {
			provideServletApi = DEF_SERVLET_API_PROVISION;
		}

		if ("true".equals(provideServletApi)) {
			servApiFilter = GrantedAuthorityDefaultsParserUtils.registerWithDefaultRolePrefix(pc, SecurityContextHolderAwareRequestFilterBeanFactory.class);
			servApiFilter.getPropertyValues().add("authenticationManager",
					authenticationManager);
		}
	}

	// Adds the jaas-api integration filter if required
	private void createJaasApiFilter() {
		final String ATT_JAAS_API_PROVISION = "jaas-api-provision";
		final String DEF_JAAS_API_PROVISION = "false";

		String provideJaasApi = httpElt.getAttribute(ATT_JAAS_API_PROVISION);
		if (!StringUtils.hasText(provideJaasApi)) {
			provideJaasApi = DEF_JAAS_API_PROVISION;
		}

		if ("true".equals(provideJaasApi)) {
			jaasApiFilter = new RootBeanDefinition(JaasApiIntegrationFilter.class);
		}
	}

	private void createChannelProcessingFilter() {
		ManagedMap<BeanMetadataElement, BeanDefinition> channelRequestMap = parseInterceptUrlsForChannelSecurity();

		if (channelRequestMap.isEmpty()) {
			return;
		}

		RootBeanDefinition channelFilter = new RootBeanDefinition(
				ChannelProcessingFilter.class);
		BeanDefinitionBuilder metadataSourceBldr = BeanDefinitionBuilder
				.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
		metadataSourceBldr.addConstructorArgValue(channelRequestMap);
		// metadataSourceBldr.addPropertyValue("stripQueryStringFromUrls", matcher
		// instanceof AntUrlPathMatcher);

		channelFilter.getPropertyValues().addPropertyValue("securityMetadataSource",
				metadataSourceBldr.getBeanDefinition());
		RootBeanDefinition channelDecisionManager = new RootBeanDefinition(
				ChannelDecisionManagerImpl.class);
		ManagedList<RootBeanDefinition> channelProcessors = new ManagedList<>(
				3);
		RootBeanDefinition secureChannelProcessor = new RootBeanDefinition(
				SecureChannelProcessor.class);
		RootBeanDefinition retryWithHttp = new RootBeanDefinition(
				RetryWithHttpEntryPoint.class);
		RootBeanDefinition retryWithHttps = new RootBeanDefinition(
				RetryWithHttpsEntryPoint.class);

		retryWithHttp.getPropertyValues().addPropertyValue("portMapper", portMapper);
		retryWithHttp.getPropertyValues().addPropertyValue("portResolver", portResolver);
		retryWithHttps.getPropertyValues().addPropertyValue("portMapper", portMapper);
		retryWithHttps.getPropertyValues().addPropertyValue("portResolver", portResolver);
		secureChannelProcessor.getPropertyValues().addPropertyValue("entryPoint",
				retryWithHttps);
		RootBeanDefinition inSecureChannelProcessor = new RootBeanDefinition(
				InsecureChannelProcessor.class);
		inSecureChannelProcessor.getPropertyValues().addPropertyValue("entryPoint",
				retryWithHttp);
		channelProcessors.add(secureChannelProcessor);
		channelProcessors.add(inSecureChannelProcessor);
		channelDecisionManager.getPropertyValues().addPropertyValue("channelProcessors",
				channelProcessors);

		String id = pc.getReaderContext().registerWithGeneratedName(
				channelDecisionManager);
		channelFilter.getPropertyValues().addPropertyValue("channelDecisionManager",
				new RuntimeBeanReference(id));
		cpf = channelFilter;
	}

	/**
	 * Parses the intercept-url elements to obtain the map used by channel security. This
	 * will be empty unless the <tt>requires-channel</tt> attribute has been used on a URL
	 * path.
	 */
	private ManagedMap<BeanMetadataElement, BeanDefinition> parseInterceptUrlsForChannelSecurity() {

		ManagedMap<BeanMetadataElement, BeanDefinition> channelRequestMap = new ManagedMap<>();

		for (Element urlElt : interceptUrls) {
			String path = urlElt.getAttribute(ATT_PATH_PATTERN);
			String method = urlElt.getAttribute(ATT_HTTP_METHOD);
			String matcherRef = urlElt.getAttribute(ATT_REQUEST_MATCHER_REF);
			boolean hasMatcherRef = StringUtils.hasText(matcherRef);

			if (!hasMatcherRef && !StringUtils.hasText(path)) {
				pc.getReaderContext().error("pattern attribute cannot be empty or null",
						urlElt);
			}

			String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);

			if (StringUtils.hasText(requiredChannel)) {
				BeanMetadataElement matcher = hasMatcherRef ? new RuntimeBeanReference(matcherRef) : matcherType.createMatcher(pc, path, method);

				RootBeanDefinition channelAttributes = new RootBeanDefinition(
						ChannelAttributeFactory.class);
				channelAttributes.getConstructorArgumentValues().addGenericArgumentValue(
						requiredChannel);
				channelAttributes.setFactoryMethodName("createChannelAttributes");

				channelRequestMap.put(matcher, channelAttributes);
			}
		}

		return channelRequestMap;
	}

	private void createRequestCacheFilter() {
		Element requestCacheElt = DomUtils.getChildElementByTagName(httpElt,
				Elements.REQUEST_CACHE);

		if (requestCacheElt != null) {
			requestCache = new RuntimeBeanReference(requestCacheElt.getAttribute(ATT_REF));
		}
		else {
			BeanDefinitionBuilder requestCacheBldr;

			if (sessionPolicy == SessionCreationPolicy.STATELESS) {
				requestCacheBldr = BeanDefinitionBuilder
						.rootBeanDefinition(NullRequestCache.class);
			}
			else {
				requestCacheBldr = BeanDefinitionBuilder
						.rootBeanDefinition(HttpSessionRequestCache.class);
				requestCacheBldr.addPropertyValue("createSessionAllowed",
						sessionPolicy == SessionCreationPolicy.IF_REQUIRED);
				requestCacheBldr.addPropertyValue("portResolver", portResolver);
				if (csrfFilter != null) {
					BeanDefinitionBuilder requestCacheMatcherBldr = BeanDefinitionBuilder
							.rootBeanDefinition(AntPathRequestMatcher.class);
					requestCacheMatcherBldr.addConstructorArgValue("/**");
					requestCacheMatcherBldr.addConstructorArgValue("GET");
					requestCacheBldr.addPropertyValue("requestMatcher",
							requestCacheMatcherBldr.getBeanDefinition());
				}
			}

			BeanDefinition bean = requestCacheBldr.getBeanDefinition();
			String id = pc.getReaderContext().generateBeanName(bean);
			pc.registerBeanComponent(new BeanComponentDefinition(bean, id));

			this.requestCache = new RuntimeBeanReference(id);
		}

		requestCacheAwareFilter = new RootBeanDefinition(RequestCacheAwareFilter.class);
		requestCacheAwareFilter.getConstructorArgumentValues().addGenericArgumentValue(
				requestCache);
	}

	private void createFilterSecurityInterceptor(BeanReference authManager) {
		boolean useExpressions = FilterInvocationSecurityMetadataSourceParser
				.isUseExpressions(httpElt);
		RootBeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser
				.createSecurityMetadataSource(interceptUrls, addAllAuth, httpElt, pc);

		RootBeanDefinition accessDecisionMgr;
		ManagedList<BeanDefinition> voters = new ManagedList<>(2);

		if (useExpressions) {
			BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder
					.rootBeanDefinition(WebExpressionVoter.class);
			// Read the expression handler from the FISMS
			RuntimeBeanReference expressionHandler = (RuntimeBeanReference) securityMds
					.getConstructorArgumentValues()
					.getArgumentValue(1, RuntimeBeanReference.class).getValue();

			expressionVoter.addPropertyValue("expressionHandler", expressionHandler);

			voters.add(expressionVoter.getBeanDefinition());
		}
		else {
			voters.add(GrantedAuthorityDefaultsParserUtils.registerWithDefaultRolePrefix(pc, RoleVoterBeanFactory.class));
			voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
		}
		accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
		accessDecisionMgr.getConstructorArgumentValues().addGenericArgumentValue(voters);
		accessDecisionMgr.setSource(pc.extractSource(httpElt));

		// Set up the access manager reference for http
		String accessManagerId = httpElt.getAttribute(ATT_ACCESS_MGR);

		if (!StringUtils.hasText(accessManagerId)) {
			accessManagerId = pc.getReaderContext().generateBeanName(accessDecisionMgr);
			pc.registerBeanComponent(new BeanComponentDefinition(accessDecisionMgr,
					accessManagerId));
		}

		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.rootBeanDefinition(FilterSecurityInterceptor.class);

		builder.addPropertyReference("accessDecisionManager", accessManagerId);
		builder.addPropertyValue("authenticationManager", authManager);

		if ("false".equals(httpElt.getAttribute(ATT_ONCE_PER_REQUEST))) {
			builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
		}

		builder.addPropertyValue("securityMetadataSource", securityMds);
		BeanDefinition fsiBean = builder.getBeanDefinition();
		String fsiId = pc.getReaderContext().generateBeanName(fsiBean);
		pc.registerBeanComponent(new BeanComponentDefinition(fsiBean, fsiId));

		// Create and register a DefaultWebInvocationPrivilegeEvaluator for use with
		// taglibs etc.
		BeanDefinition wipe = new RootBeanDefinition(
				DefaultWebInvocationPrivilegeEvaluator.class);
		wipe.getConstructorArgumentValues().addGenericArgumentValue(
				new RuntimeBeanReference(fsiId));

		pc.registerBeanComponent(new BeanComponentDefinition(wipe, pc.getReaderContext()
				.generateBeanName(wipe)));

		this.fsi = new RuntimeBeanReference(fsiId);
	}

	private void createAddHeadersFilter() {
		Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.HEADERS);
		this.addHeadersFilter = new HeadersBeanDefinitionParser().parse(elmt, pc);
	}

	private void createCorsFilter() {
		Element elmt = DomUtils.getChildElementByTagName(this.httpElt, Elements.CORS);
		this.corsFilter = new CorsBeanDefinitionParser().parse(elmt, this.pc);

	}

	private void createCsrfFilter() {
		Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.CSRF);
		csrfParser = new CsrfBeanDefinitionParser();
		csrfFilter = csrfParser.parse(elmt, pc);

		if (csrfFilter == null) {
			csrfParser = null;
			return;
		}

		this.csrfAuthStrategy = csrfParser.getCsrfAuthenticationStrategy();
		this.csrfLogoutHandler = csrfParser.getCsrfLogoutHandler();
	}

	BeanMetadataElement getCsrfLogoutHandler() {
		return this.csrfLogoutHandler;
	}

	BeanReference getSessionStrategy() {
		return sessionStrategyRef;
	}

	SessionCreationPolicy getSessionCreationPolicy() {
		return sessionPolicy;
	}

	BeanReference getRequestCache() {
		return requestCache;
	}

	List<OrderDecorator> getFilters() {
		List<OrderDecorator> filters = new ArrayList<>();

		if (cpf != null) {
			filters.add(new OrderDecorator(cpf, CHANNEL_FILTER));
		}

		if (concurrentSessionFilter != null) {
			filters.add(new OrderDecorator(concurrentSessionFilter,
					CONCURRENT_SESSION_FILTER));
		}

		if (webAsyncManagerFilter != null) {
			filters.add(new OrderDecorator(webAsyncManagerFilter,
					WEB_ASYNC_MANAGER_FILTER));
		}

		filters.add(new OrderDecorator(securityContextPersistenceFilter,
				SECURITY_CONTEXT_FILTER));

		if (servApiFilter != null) {
			filters.add(new OrderDecorator(servApiFilter, SERVLET_API_SUPPORT_FILTER));
		}

		if (jaasApiFilter != null) {
			filters.add(new OrderDecorator(jaasApiFilter, JAAS_API_SUPPORT_FILTER));
		}

		if (sfpf != null) {
			filters.add(new OrderDecorator(sfpf, SESSION_MANAGEMENT_FILTER));
		}

		filters.add(new OrderDecorator(fsi, FILTER_SECURITY_INTERCEPTOR));

		if (sessionPolicy != SessionCreationPolicy.STATELESS) {
			filters.add(new OrderDecorator(requestCacheAwareFilter, REQUEST_CACHE_FILTER));
		}

		if (this.corsFilter != null) {
			filters.add(new OrderDecorator(this.corsFilter, CORS_FILTER));
		}

		if (addHeadersFilter != null) {
			filters.add(new OrderDecorator(addHeadersFilter, HEADERS_FILTER));
		}

		if (csrfFilter != null) {
			filters.add(new OrderDecorator(csrfFilter, CSRF_FILTER));
		}

		return filters;
	}

	static class RoleVoterBeanFactory extends AbstractGrantedAuthorityDefaultsBeanFactory {
		private RoleVoter voter = new RoleVoter();

		public RoleVoter getBean() {
			voter.setRolePrefix(this.rolePrefix);
			return voter;
		}
	}

	static class SecurityContextHolderAwareRequestFilterBeanFactory extends GrantedAuthorityDefaultsParserUtils.AbstractGrantedAuthorityDefaultsBeanFactory {
		private SecurityContextHolderAwareRequestFilter filter = new SecurityContextHolderAwareRequestFilter();

		public SecurityContextHolderAwareRequestFilter getBean() {
			filter.setRolePrefix(this.rolePrefix);
			return filter;
		}
	}
}
