/*
 * 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.messaging.access.expression;

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

import org.springframework.expression.Expression;
import org.springframework.messaging.Message;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.messaging.access.intercept.DefaultMessageSecurityMetadataSource;
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
import org.springframework.security.messaging.util.matcher.MessageMatcher;

/**
 * A class used to create a {@link MessageSecurityMetadataSource} that uses
 * {@link MessageMatcher} mapped to Spring Expressions.
 *
 * @since 4.0
 * @author Rob Winch
 */
public final class ExpressionBasedMessageSecurityMetadataSourceFactory {

	/**
	 * Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher}
	 * mapped to Spring Expressions. Each entry is considered in order and only the first
	 * match is used.
	 *
	 * For example:
	 *
	 * <pre>
	 *     LinkedHashMap&lt;MessageMatcher&lt;?&gt;,String&gt; matcherToExpression = new LinkedHashMap&lt;MessageMatcher&lt;Object&gt;,String&gt;();
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
	 *
	 *     MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
	 * </pre>
	 *
	 * <p>
	 * If our destination is "/public/hello", it would match on "/public/**" and on "/**".
	 * However, only "/public/**" would be used since it is the first entry. That means
	 * that a destination of "/public/hello" will be mapped to "permitAll".
	 *
	 * <p>
	 * For a complete listing of expressions see {@link MessageSecurityExpressionRoot}
	 *
	 * @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings
	 * that are turned into an Expression using
	 * {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()}
	 * @return the {@link MessageSecurityMetadataSource} to use. Cannot be null.
	 */
	public static MessageSecurityMetadataSource createExpressionMessageMetadataSource(
			LinkedHashMap<MessageMatcher<?>, String> matcherToExpression) {
		return createExpressionMessageMetadataSource(matcherToExpression, new DefaultMessageSecurityExpressionHandler<>());
	}

	/**
	 * Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher}
	 * mapped to Spring Expressions. Each entry is considered in order and only the first
	 * match is used.
	 *
	 * For example:
	 *
	 * <pre>
	 *     LinkedHashMap&lt;MessageMatcher&lt;?&gt;,String&gt; matcherToExpression = new LinkedHashMap&lt;MessageMatcher&lt;Object&gt;,String&gt;();
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
	 *
	 *     MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
	 * </pre>
	 *
	 * <p>
	 * If our destination is "/public/hello", it would match on "/public/**" and on "/**".
	 * However, only "/public/**" would be used since it is the first entry. That means
	 * that a destination of "/public/hello" will be mapped to "permitAll".
	 * </p>
	 *
	 * <p>
	 * For a complete listing of expressions see {@link MessageSecurityExpressionRoot}
	 * </p>
	 *
	 * @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings
	 * that are turned into an Expression using
	 * {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()}
	 * @param handler the {@link SecurityExpressionHandler} to use
	 * @return the {@link MessageSecurityMetadataSource} to use. Cannot be null.
	 */
	public static MessageSecurityMetadataSource createExpressionMessageMetadataSource(
			LinkedHashMap<MessageMatcher<?>, String> matcherToExpression, SecurityExpressionHandler<Message<Object>> handler) {

		LinkedHashMap<MessageMatcher<?>, Collection<ConfigAttribute>> matcherToAttrs = new LinkedHashMap<MessageMatcher<?>, Collection<ConfigAttribute>>();

		for (Map.Entry<MessageMatcher<?>, String> entry : matcherToExpression.entrySet()) {
			MessageMatcher<?> matcher = entry.getKey();
			String rawExpression = entry.getValue();
			Expression expression = handler.getExpressionParser().parseExpression(
					rawExpression);
			ConfigAttribute attribute = new MessageExpressionConfigAttribute(expression);
			matcherToAttrs.put(matcher, Arrays.asList(attribute));
		}
		return new DefaultMessageSecurityMetadataSource(matcherToAttrs);
	}

	private ExpressionBasedMessageSecurityMetadataSourceFactory() {
	}
}
