/*
 * Copyright 2002-2016 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.web.access.expression;

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

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.matcher.RequestVariablesExtractor;
import org.springframework.util.Assert;

/**
 * Expression-based {@code FilterInvocationSecurityMetadataSource}.
 *
 * @author Luke Taylor
 * @since 3.0
 */
public final class ExpressionBasedFilterInvocationSecurityMetadataSource
		extends DefaultFilterInvocationSecurityMetadataSource {
	private final static Log logger = LogFactory
			.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class);

	public ExpressionBasedFilterInvocationSecurityMetadataSource(
			LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap,
			SecurityExpressionHandler<FilterInvocation> expressionHandler) {
		super(processMap(requestMap, expressionHandler.getExpressionParser()));
		Assert.notNull(expressionHandler,
				"A non-null SecurityExpressionHandler is required");
	}

	private static LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> processMap(
			LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap,
			ExpressionParser parser) {
		Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");

		LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestToExpressionAttributesMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>(
				requestMap);

		for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
				.entrySet()) {
			RequestMatcher request = entry.getKey();
			Assert.isTrue(entry.getValue().size() == 1,
					"Expected a single expression attribute for " + request);
			ArrayList<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(1);
			String expression = entry.getValue().toArray(new ConfigAttribute[1])[0]
					.getAttribute();
			logger.debug("Adding web access control expression '" + expression + "', for "
					+ request);

			AbstractVariableEvaluationContextPostProcessor postProcessor = createPostProcessor(
					request);
			try {
				attributes.add(new WebExpressionConfigAttribute(
						parser.parseExpression(expression), postProcessor));
			}
			catch (ParseException e) {
				throw new IllegalArgumentException(
						"Failed to parse expression '" + expression + "'");
			}

			requestToExpressionAttributesMap.put(request, attributes);
		}

		return requestToExpressionAttributesMap;
	}

	private static AbstractVariableEvaluationContextPostProcessor createPostProcessor(
			Object request) {
		if (request instanceof RequestVariablesExtractor) {
			return new RequestVariablesExtractorEvaluationContextPostProcessor(
					(RequestVariablesExtractor) request);
		}
		return null;
	}

	static class AntPathMatcherEvaluationContextPostProcessor
			extends AbstractVariableEvaluationContextPostProcessor {
		private final AntPathRequestMatcher matcher;

		public AntPathMatcherEvaluationContextPostProcessor(
				AntPathRequestMatcher matcher) {
			this.matcher = matcher;
		}

		@Override
		Map<String, String> extractVariables(HttpServletRequest request) {
			return this.matcher.extractUriTemplateVariables(request);
		}
	}

	static class RequestVariablesExtractorEvaluationContextPostProcessor
			extends AbstractVariableEvaluationContextPostProcessor {
		private final RequestVariablesExtractor matcher;

		public RequestVariablesExtractorEvaluationContextPostProcessor(
				RequestVariablesExtractor matcher) {
			this.matcher = matcher;
		}

		@Override
		Map<String, String> extractVariables(HttpServletRequest request) {
			return this.matcher.extractUriTemplateVariables(request);
		}
	}

}
