/*
 * Copyright 2002-2022 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
 *
 *      https://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.method;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;

import org.springframework.aop.config.AopNamespaceUtils;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.target.LazyInitTargetSource;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
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.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.log.LogMessage;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
import org.springframework.security.access.annotation.Jsr250Voter;
import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory;
import org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice;
import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.AfterInvocationProviderManager;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor;
import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor;
import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource;
import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource;
import org.springframework.security.access.prepost.PostInvocationAdviceProvider;
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter;
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
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.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.Elements;
import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;

/**
 * Processes the top-level "global-method-security" element.
 *
 * @author Ben Alex
 * @author Luke Taylor
 * @author Rob Winch
 * @since 2.0
 * @deprecated Use {@link MethodSecurityBeanDefinitionParser} instead
 */
@Deprecated
public class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {

	private final Log logger = LogFactory.getLog(getClass());

	private static final String ATT_AUTHENTICATION_MANAGER_REF = "authentication-manager-ref";

	private static final String ATT_ACCESS = "access";

	private static final String ATT_EXPRESSION = "expression";

	private static final String ATT_ACCESS_MGR = "access-decision-manager-ref";

	private static final String ATT_RUN_AS_MGR = "run-as-manager-ref";

	private static final String ATT_USE_JSR250 = "jsr250-annotations";

	private static final String ATT_USE_SECURED = "secured-annotations";

	private static final String ATT_USE_PREPOST = "pre-post-annotations";

	private static final String ATT_REF = "ref";

	private static final String ATT_MODE = "mode";

	private static final String ATT_ADVICE_ORDER = "order";

	private static final String ATT_META_DATA_SOURCE_REF = "metadata-source-ref";

	@Override
	public BeanDefinition parse(Element element, ParserContext pc) {
		CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
				pc.extractSource(element));
		pc.pushContainingComponent(compositeDef);
		Object source = pc.extractSource(element);
		// The list of method metadata delegates
		ManagedList<BeanMetadataElement> delegates = new ManagedList<>();
		boolean jsr250Enabled = "enabled".equals(element.getAttribute(ATT_USE_JSR250));
		boolean useSecured = "enabled".equals(element.getAttribute(ATT_USE_SECURED));
		boolean prePostAnnotationsEnabled = "enabled".equals(element.getAttribute(ATT_USE_PREPOST));
		boolean useAspectJ = "aspectj".equals(element.getAttribute(ATT_MODE));
		BeanDefinition preInvocationVoter = null;
		ManagedList<BeanMetadataElement> afterInvocationProviders = new ManagedList<>();
		// Check for an external SecurityMetadataSource, which takes priority over other
		// sources
		String metaDataSourceId = element.getAttribute(ATT_META_DATA_SOURCE_REF);
		if (StringUtils.hasText(metaDataSourceId)) {
			delegates.add(new RuntimeBeanReference(metaDataSourceId));
		}
		if (prePostAnnotationsEnabled) {
			Element prePostElt = DomUtils.getChildElementByTagName(element, Elements.INVOCATION_HANDLING);
			Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
			if (prePostElt != null && expressionHandlerElt != null) {
				pc.getReaderContext().error(Elements.INVOCATION_HANDLING + " and " + Elements.EXPRESSION_HANDLER
						+ " cannot be used together ", source);
			}
			BeanDefinitionBuilder preInvocationVoterBldr = BeanDefinitionBuilder
					.rootBeanDefinition(PreInvocationAuthorizationAdviceVoter.class);
			// After-invocation provider to handle post-invocation filtering and
			// authorization expression annotations.
			BeanDefinitionBuilder afterInvocationBldr = BeanDefinitionBuilder
					.rootBeanDefinition(PostInvocationAdviceProvider.class);
			// The metadata source for the security interceptor
			BeanDefinitionBuilder mds = BeanDefinitionBuilder
					.rootBeanDefinition(PrePostAnnotationSecurityMetadataSource.class);
			if (prePostElt != null) {
				// Customized override of expression handling system
				String attributeFactoryRef = DomUtils
						.getChildElementByTagName(prePostElt, Elements.INVOCATION_ATTRIBUTE_FACTORY)
						.getAttribute("ref");
				String preAdviceRef = DomUtils.getChildElementByTagName(prePostElt, Elements.PRE_INVOCATION_ADVICE)
						.getAttribute("ref");
				String postAdviceRef = DomUtils.getChildElementByTagName(prePostElt, Elements.POST_INVOCATION_ADVICE)
						.getAttribute("ref");
				mds.addConstructorArgReference(attributeFactoryRef);
				preInvocationVoterBldr.addConstructorArgReference(preAdviceRef);
				afterInvocationBldr.addConstructorArgReference(postAdviceRef);
			}
			else {
				// The default expression-based system
				String expressionHandlerRef = (expressionHandlerElt != null) ? expressionHandlerElt.getAttribute("ref")
						: null;
				if (StringUtils.hasText(expressionHandlerRef)) {
					this.logger.info(LogMessage.format("Using bean '%s' as method ExpressionHandler implementation",
							expressionHandlerRef));
					RootBeanDefinition lazyInitPP = new RootBeanDefinition(
							LazyInitBeanDefinitionRegistryPostProcessor.class);
					lazyInitPP.getConstructorArgumentValues().addGenericArgumentValue(expressionHandlerRef);
					pc.getReaderContext().registerWithGeneratedName(lazyInitPP);
					BeanDefinitionBuilder lazyMethodSecurityExpressionHandlerBldr = BeanDefinitionBuilder
							.rootBeanDefinition(LazyInitTargetSource.class);
					lazyMethodSecurityExpressionHandlerBldr.addPropertyValue("targetBeanName", expressionHandlerRef);
					BeanDefinitionBuilder expressionHandlerProxyBldr = BeanDefinitionBuilder
							.rootBeanDefinition(ProxyFactoryBean.class);
					expressionHandlerProxyBldr.addPropertyValue("targetSource",
							lazyMethodSecurityExpressionHandlerBldr.getBeanDefinition());
					expressionHandlerProxyBldr.addPropertyValue("proxyInterfaces",
							MethodSecurityExpressionHandler.class);
					expressionHandlerRef = pc.getReaderContext()
							.generateBeanName(expressionHandlerProxyBldr.getBeanDefinition());
					pc.registerBeanComponent(new BeanComponentDefinition(expressionHandlerProxyBldr.getBeanDefinition(),
							expressionHandlerRef));
				}
				else {
					RootBeanDefinition expressionHandler = registerWithDefaultRolePrefix(pc,
							DefaultMethodSecurityExpressionHandlerBeanFactory.class);
					expressionHandlerRef = pc.getReaderContext().generateBeanName(expressionHandler);
					pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));
					this.logger.info("Expressions were enabled for method security but no SecurityExpressionHandler "
							+ "was configured. All hasPermission() expressions will evaluate to false.");
				}
				BeanDefinitionBuilder expressionPreAdviceBldr = BeanDefinitionBuilder
						.rootBeanDefinition(ExpressionBasedPreInvocationAdvice.class);
				expressionPreAdviceBldr.addPropertyReference("expressionHandler", expressionHandlerRef);
				preInvocationVoterBldr.addConstructorArgValue(expressionPreAdviceBldr.getBeanDefinition());
				BeanDefinitionBuilder expressionPostAdviceBldr = BeanDefinitionBuilder
						.rootBeanDefinition(ExpressionBasedPostInvocationAdvice.class);
				expressionPostAdviceBldr.addConstructorArgReference(expressionHandlerRef);
				afterInvocationBldr.addConstructorArgValue(expressionPostAdviceBldr.getBeanDefinition());
				BeanDefinitionBuilder annotationInvocationFactory = BeanDefinitionBuilder
						.rootBeanDefinition(ExpressionBasedAnnotationAttributeFactory.class);
				annotationInvocationFactory.addConstructorArgReference(expressionHandlerRef);
				mds.addConstructorArgValue(annotationInvocationFactory.getBeanDefinition());
			}
			preInvocationVoter = preInvocationVoterBldr.getBeanDefinition();
			afterInvocationProviders.add(afterInvocationBldr.getBeanDefinition());
			delegates.add(mds.getBeanDefinition());
		}
		if (useSecured) {
			delegates.add(BeanDefinitionBuilder.rootBeanDefinition(SecuredAnnotationSecurityMetadataSource.class)
					.getBeanDefinition());
		}
		if (jsr250Enabled) {
			RootBeanDefinition jsrMetadataSource = registerWithDefaultRolePrefix(pc,
					Jsr250MethodSecurityMetadataSourceBeanFactory.class);
			delegates.add(jsrMetadataSource);
		}
		// Now create a Map<String, ConfigAttribute> for each <protect-pointcut>
		// sub-element
		Map<String, List<ConfigAttribute>> pointcutMap = parseProtectPointcuts(pc,
				DomUtils.getChildElementsByTagName(element, Elements.PROTECT_POINTCUT));
		if (pointcutMap.size() > 0) {
			if (useAspectJ) {
				pc.getReaderContext().error("You can't use AspectJ mode with protect-pointcut definitions", source);
			}
			// Only add it if there are actually any pointcuts defined.
			BeanDefinition mapBasedMetadataSource = new RootBeanDefinition(MapBasedMethodSecurityMetadataSource.class);
			BeanReference ref = new RuntimeBeanReference(
					pc.getReaderContext().generateBeanName(mapBasedMetadataSource));
			delegates.add(ref);
			pc.registerBeanComponent(new BeanComponentDefinition(mapBasedMetadataSource, ref.getBeanName()));
			registerProtectPointcutPostProcessor(pc, pointcutMap, ref, source);
		}
		BeanReference metadataSource = registerDelegatingMethodSecurityMetadataSource(pc, delegates, source);
		// Check for additional after-invocation-providers..
		List<Element> afterInvocationElts = DomUtils.getChildElementsByTagName(element,
				Elements.AFTER_INVOCATION_PROVIDER);
		for (Element elt : afterInvocationElts) {
			afterInvocationProviders.add(new RuntimeBeanReference(elt.getAttribute(ATT_REF)));
		}
		String accessManagerId = element.getAttribute(ATT_ACCESS_MGR);
		if (!StringUtils.hasText(accessManagerId)) {
			accessManagerId = registerAccessManager(pc, jsr250Enabled, preInvocationVoter);
		}
		String authMgrRef = element.getAttribute(ATT_AUTHENTICATION_MANAGER_REF);
		String runAsManagerId = element.getAttribute(ATT_RUN_AS_MGR);
		BeanReference interceptor = registerMethodSecurityInterceptor(pc, authMgrRef, accessManagerId, runAsManagerId,
				metadataSource, afterInvocationProviders, source, useAspectJ);
		if (useAspectJ) {
			BeanDefinitionBuilder aspect = BeanDefinitionBuilder.rootBeanDefinition(
					"org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect");
			aspect.setFactoryMethod("aspectOf");
			aspect.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			aspect.addPropertyValue("securityInterceptor", interceptor);
			String id = pc.getReaderContext().registerWithGeneratedName(aspect.getBeanDefinition());
			pc.registerBeanComponent(new BeanComponentDefinition(aspect.getBeanDefinition(), id));
		}
		else {
			registerAdvisor(pc, interceptor, metadataSource, source, element.getAttribute(ATT_ADVICE_ORDER));
			AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(pc, element);
		}
		pc.popAndRegisterContainingComponent();
		return null;
	}

	/**
	 * Register the default AccessDecisionManager. Adds the special JSR 250 voter jsr-250
	 * is enabled and an expression voter if expression-based access control is enabled.
	 * @return
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private String registerAccessManager(ParserContext pc, boolean jsr250Enabled, BeanDefinition expressionVoter) {
		BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class);
		ManagedList voters = new ManagedList(4);
		if (expressionVoter != null) {
			voters.add(expressionVoter);
		}
		voters.add(new RootBeanDefinition(RoleVoter.class));
		voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
		if (jsr250Enabled) {
			voters.add(new RootBeanDefinition(Jsr250Voter.class));
		}
		accessMgrBuilder.addConstructorArgValue(voters);
		BeanDefinition accessManager = accessMgrBuilder.getBeanDefinition();
		String id = pc.getReaderContext().generateBeanName(accessManager);
		pc.registerBeanComponent(new BeanComponentDefinition(accessManager, id));
		return id;
	}

	@SuppressWarnings("rawtypes")
	private BeanReference registerDelegatingMethodSecurityMetadataSource(ParserContext pc, ManagedList delegates,
			Object source) {
		RootBeanDefinition delegatingMethodSecurityMetadataSource = new RootBeanDefinition(
				DelegatingMethodSecurityMetadataSource.class);
		delegatingMethodSecurityMetadataSource.setSource(source);
		delegatingMethodSecurityMetadataSource.getConstructorArgumentValues().addGenericArgumentValue(delegates);
		String id = pc.getReaderContext().generateBeanName(delegatingMethodSecurityMetadataSource);
		pc.registerBeanComponent(new BeanComponentDefinition(delegatingMethodSecurityMetadataSource, id));

		return new RuntimeBeanReference(id);
	}

	private void registerProtectPointcutPostProcessor(ParserContext parserContext,
			Map<String, List<ConfigAttribute>> pointcutMap, BeanReference mapBasedMethodSecurityMetadataSource,
			Object source) {
		RootBeanDefinition ppbp = new RootBeanDefinition(ProtectPointcutPostProcessor.class);
		ppbp.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		ppbp.setSource(source);
		ppbp.getConstructorArgumentValues().addGenericArgumentValue(mapBasedMethodSecurityMetadataSource);
		ppbp.getPropertyValues().addPropertyValue("pointcutMap", pointcutMap);
		parserContext.getReaderContext().registerWithGeneratedName(ppbp);
	}

	private Map<String, List<ConfigAttribute>> parseProtectPointcuts(ParserContext parserContext,
			List<Element> protectPointcutElts) {
		Map<String, List<ConfigAttribute>> pointcutMap = new LinkedHashMap<>();
		for (Element childElt : protectPointcutElts) {
			String accessConfig = childElt.getAttribute(ATT_ACCESS);
			String expression = childElt.getAttribute(ATT_EXPRESSION);
			if (!StringUtils.hasText(accessConfig)) {
				parserContext.getReaderContext().error("Access configuration required",
						parserContext.extractSource(childElt));
			}
			if (!StringUtils.hasText(expression)) {
				parserContext.getReaderContext().error("Pointcut expression required",
						parserContext.extractSource(childElt));
			}
			String[] attributeTokens = StringUtils.commaDelimitedListToStringArray(accessConfig);
			List<ConfigAttribute> attributes = new ArrayList<>(attributeTokens.length);
			for (String token : attributeTokens) {
				attributes.add(new SecurityConfig(token));
			}
			pointcutMap.put(expression, attributes);
		}
		return pointcutMap;
	}

	private BeanReference registerMethodSecurityInterceptor(ParserContext pc, String authMgrRef, String accessManagerId,
			String runAsManagerId, BeanReference metadataSource, List<BeanMetadataElement> afterInvocationProviders,
			Object source, boolean useAspectJ) {
		BeanDefinitionBuilder bldr = BeanDefinitionBuilder.rootBeanDefinition(
				useAspectJ ? AspectJMethodSecurityInterceptor.class : MethodSecurityInterceptor.class);
		bldr.getRawBeanDefinition().setSource(source);
		bldr.addPropertyReference("accessDecisionManager", accessManagerId);
		RootBeanDefinition authMgr = new RootBeanDefinition(AuthenticationManagerDelegator.class);
		authMgr.getConstructorArgumentValues().addGenericArgumentValue(authMgrRef);
		bldr.addPropertyValue("authenticationManager", authMgr);
		bldr.addPropertyValue("securityMetadataSource", metadataSource);
		if (StringUtils.hasText(runAsManagerId)) {
			bldr.addPropertyReference("runAsManager", runAsManagerId);
		}
		if (!afterInvocationProviders.isEmpty()) {
			BeanDefinition afterInvocationManager;
			afterInvocationManager = new RootBeanDefinition(AfterInvocationProviderManager.class);
			afterInvocationManager.getPropertyValues().addPropertyValue("providers", afterInvocationProviders);
			bldr.addPropertyValue("afterInvocationManager", afterInvocationManager);
		}
		BeanDefinition bean = bldr.getBeanDefinition();
		String id = pc.getReaderContext().generateBeanName(bean);
		pc.registerBeanComponent(new BeanComponentDefinition(bean, id));
		return new RuntimeBeanReference(id);
	}

	private void registerAdvisor(ParserContext parserContext, BeanReference interceptor, BeanReference metadataSource,
			Object source, String adviceOrder) {
		if (parserContext.getRegistry().containsBeanDefinition(BeanIds.METHOD_SECURITY_METADATA_SOURCE_ADVISOR)) {
			parserContext.getReaderContext().error("Duplicate <global-method-security> detected.", source);
		}
		RootBeanDefinition advisor = new RootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);
		if (StringUtils.hasText(adviceOrder)) {
			advisor.getPropertyValues().addPropertyValue("order", adviceOrder);
		}
		// advisor must be an infrastructure bean as Spring's
		// InfrastructureAdvisorAutoProxyCreator will ignore it
		// otherwise
		advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		advisor.setSource(source);
		advisor.getConstructorArgumentValues().addGenericArgumentValue(interceptor.getBeanName());
		advisor.getConstructorArgumentValues().addGenericArgumentValue(metadataSource);
		advisor.getConstructorArgumentValues().addGenericArgumentValue(metadataSource.getBeanName());
		parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_METADATA_SOURCE_ADVISOR, advisor);
	}

	private RootBeanDefinition registerWithDefaultRolePrefix(ParserContext pc,
			Class<? extends AbstractGrantedAuthorityDefaultsBeanFactory> beanFactoryClass) {
		RootBeanDefinition beanFactoryDefinition = new RootBeanDefinition(beanFactoryClass);
		String beanFactoryRef = pc.getReaderContext().generateBeanName(beanFactoryDefinition);
		pc.getRegistry().registerBeanDefinition(beanFactoryRef, beanFactoryDefinition);
		RootBeanDefinition bean = new RootBeanDefinition();
		bean.setFactoryBeanName(beanFactoryRef);
		bean.setFactoryMethodName("getBean");
		return bean;
	}

	/**
	 * Delays the lookup of the AuthenticationManager within MethodSecurityInterceptor, to
	 * prevent issues like SEC-933.
	 *
	 * @author Luke Taylor
	 * @since 3.0
	 */
	static final class AuthenticationManagerDelegator implements AuthenticationManager, BeanFactoryAware {

		private AuthenticationManager delegate;

		private final Object delegateMonitor = new Object();

		private BeanFactory beanFactory;

		private final String authMgrBean;

		AuthenticationManagerDelegator(String authMgrBean) {
			this.authMgrBean = StringUtils.hasText(authMgrBean) ? authMgrBean : BeanIds.AUTHENTICATION_MANAGER;
		}

		@Override
		public Authentication authenticate(Authentication authentication) throws AuthenticationException {
			synchronized (this.delegateMonitor) {
				if (this.delegate == null) {
					Assert.state(this.beanFactory != null,
							() -> "BeanFactory must be set to resolve " + this.authMgrBean);
					try {
						this.delegate = this.beanFactory.getBean(this.authMgrBean, AuthenticationManager.class);
					}
					catch (NoSuchBeanDefinitionException ex) {
						if (BeanIds.AUTHENTICATION_MANAGER.equals(ex.getBeanName())) {
							throw new NoSuchBeanDefinitionException(BeanIds.AUTHENTICATION_MANAGER,
									AuthenticationManagerFactoryBean.MISSING_BEAN_ERROR_MESSAGE);
						}
						throw ex;
					}
				}
			}
			return this.delegate.authenticate(authentication);
		}

		@Override
		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
			this.beanFactory = beanFactory;
		}

	}

	static class Jsr250MethodSecurityMetadataSourceBeanFactory extends AbstractGrantedAuthorityDefaultsBeanFactory {

		private Jsr250MethodSecurityMetadataSource source = new Jsr250MethodSecurityMetadataSource();

		Jsr250MethodSecurityMetadataSource getBean() {
			this.source.setDefaultRolePrefix(this.rolePrefix);
			return this.source;
		}

	}

	static class DefaultMethodSecurityExpressionHandlerBeanFactory extends AbstractGrantedAuthorityDefaultsBeanFactory {

		private DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();

		DefaultMethodSecurityExpressionHandler getBean() {
			this.handler.setDefaultRolePrefix(this.rolePrefix);
			return this.handler;
		}

	}

	abstract static class AbstractGrantedAuthorityDefaultsBeanFactory implements ApplicationContextAware {

		protected String rolePrefix = "ROLE_";

		@Override
		public final void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
			String[] grantedAuthorityDefaultsBeanNames = applicationContext
					.getBeanNamesForType(GrantedAuthorityDefaults.class);
			if (grantedAuthorityDefaultsBeanNames.length == 1) {
				GrantedAuthorityDefaults grantedAuthorityDefaults = applicationContext
						.getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
				this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
			}
		}

	}

	/**
	 * Delays setting a bean of a given name to be lazyily initialized until after all the
	 * beans are registered.
	 *
	 * @author Rob Winch
	 * @since 3.2
	 */
	private static final class LazyInitBeanDefinitionRegistryPostProcessor
			implements BeanDefinitionRegistryPostProcessor {

		private final String beanName;

		private LazyInitBeanDefinitionRegistryPostProcessor(String beanName) {
			this.beanName = beanName;
		}

		@Override
		public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
			if (!registry.containsBeanDefinition(this.beanName)) {
				return;
			}
			BeanDefinition beanDefinition = registry.getBeanDefinition(this.beanName);
			beanDefinition.setLazyInit(true);
		}

		@Override
		public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		}

	}

}
