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

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.security.access.prepost.PostInvocationAttribute;
import org.springframework.security.access.prepost.PreInvocationAttribute;
import org.springframework.security.access.prepost.PrePostInvocationAttributeFactory;

/**
 * {@link PrePostInvocationAttributeFactory} which interprets the annotation value as an
 * expression to be evaluated at runtime.
 *
 * @author Luke Taylor
 * @author Rob Winch
 * @since 3.0
 */
public class ExpressionBasedAnnotationAttributeFactory implements
		PrePostInvocationAttributeFactory {
	private final Object parserLock = new Object();
	private ExpressionParser parser;
	private MethodSecurityExpressionHandler handler;

	public ExpressionBasedAnnotationAttributeFactory(
			MethodSecurityExpressionHandler handler) {
		this.handler = handler;
	}

	public PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute,
			String filterObject, String preAuthorizeAttribute) {
		try {
			// TODO: Optimization of permitAll
			ExpressionParser parser = getParser();
			Expression preAuthorizeExpression = preAuthorizeAttribute == null ? parser
					.parseExpression("permitAll") : parser
					.parseExpression(preAuthorizeAttribute);
			Expression preFilterExpression = preFilterAttribute == null ? null : parser
					.parseExpression(preFilterAttribute);
			return new PreInvocationExpressionAttribute(preFilterExpression,
					filterObject, preAuthorizeExpression);
		}
		catch (ParseException e) {
			throw new IllegalArgumentException("Failed to parse expression '"
					+ e.getExpressionString() + "'", e);
		}
	}

	public PostInvocationAttribute createPostInvocationAttribute(
			String postFilterAttribute, String postAuthorizeAttribute) {
		try {
			ExpressionParser parser = getParser();
			Expression postAuthorizeExpression = postAuthorizeAttribute == null ? null
					: parser.parseExpression(postAuthorizeAttribute);
			Expression postFilterExpression = postFilterAttribute == null ? null : parser
					.parseExpression(postFilterAttribute);

			if (postFilterExpression != null || postAuthorizeExpression != null) {
				return new PostInvocationExpressionAttribute(postFilterExpression,
						postAuthorizeExpression);
			}
		}
		catch (ParseException e) {
			throw new IllegalArgumentException("Failed to parse expression '"
					+ e.getExpressionString() + "'", e);
		}

		return null;
	}

	/**
	 * Delay the lookup of the {@link ExpressionParser} to prevent SEC-2136
	 *
	 * @return
	 */
	private ExpressionParser getParser() {
		if (this.parser != null) {
			return this.parser;
		}
		synchronized (parserLock) {
			this.parser = handler.getExpressionParser();
			this.handler = null;
		}
		return this.parser;
	}
}
