/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * 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.intercept;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

import java.util.Collection;
import java.util.List;
import java.util.Vector;

import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.AfterInvocationProvider;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.Authentication;
import org.springframework.security.util.SimpleMethodInvocation;

/**
 * Tests {@link AfterInvocationProviderManager}.
 *
 * @author Ben Alex
 */
@SuppressWarnings("unchecked")
public class AfterInvocationProviderManagerTests {

	// ~ Methods
	// ========================================================================================================
	@Test
	public void testCorrectOperation() throws Exception {
		AfterInvocationProviderManager manager = new AfterInvocationProviderManager();
		List list = new Vector();
		list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP1")));
		list.add(new MockAfterInvocationProvider("swap2", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP2")));
		list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP3")));
		manager.setProviders(list);
		assertThat(manager.getProviders()).isEqualTo(list);
		manager.afterPropertiesSet();

		List<ConfigAttribute> attr1 = SecurityConfig.createList(
				new String[] { "GIVE_ME_SWAP1" });
		List<ConfigAttribute> attr2 = SecurityConfig.createList(
				new String[] { "GIVE_ME_SWAP2" });
		List<ConfigAttribute> attr3 = SecurityConfig.createList(
				new String[] { "GIVE_ME_SWAP3" });
		List<ConfigAttribute> attr2and3 = SecurityConfig.createList(
				new String[] { "GIVE_ME_SWAP2", "GIVE_ME_SWAP3" });
		List<ConfigAttribute> attr4 = SecurityConfig.createList(
				new String[] { "NEVER_CAUSES_SWAP" });

		assertThat(manager.decide(null, new SimpleMethodInvocation(), attr1,
				"content-before-swapping")).isEqualTo("swap1");

		assertThat(manager.decide(null, new SimpleMethodInvocation(), attr2,
				"content-before-swapping")).isEqualTo("swap2");

		assertThat(manager.decide(null, new SimpleMethodInvocation(), attr3,
				"content-before-swapping")).isEqualTo("swap3");

		assertThat(manager.decide(null, new SimpleMethodInvocation(), attr4,
				"content-before-swapping")).isEqualTo("content-before-swapping");

		assertThat(manager.decide(null, new SimpleMethodInvocation(), attr2and3,
				"content-before-swapping")).isEqualTo("swap3");
	}

	@Test
	public void testRejectsEmptyProvidersList() {
		AfterInvocationProviderManager manager = new AfterInvocationProviderManager();
		List list = new Vector();

		try {
			manager.setProviders(list);
			fail("Should have thrown IllegalArgumentException");
		}
		catch (IllegalArgumentException expected) {
			assertThat(true).isTrue();
		}
	}

	@Test
	public void testRejectsNonAfterInvocationProviders() {
		AfterInvocationProviderManager manager = new AfterInvocationProviderManager();
		List list = new Vector();
		list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP1")));
		list.add(Integer.valueOf(45));
		list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP3")));

		try {
			manager.setProviders(list);
			fail("Should have thrown IllegalArgumentException");
		}
		catch (IllegalArgumentException expected) {
			assertThat(true).isTrue();
		}
	}

	@Test
	public void testRejectsNullProvidersList() throws Exception {
		AfterInvocationProviderManager manager = new AfterInvocationProviderManager();

		try {
			manager.afterPropertiesSet();
			fail("Should have thrown IllegalArgumentException");
		}
		catch (IllegalArgumentException expected) {
			assertThat(true).isTrue();
		}
	}

	@Test
	public void testSupportsConfigAttributeIteration() throws Exception {
		AfterInvocationProviderManager manager = new AfterInvocationProviderManager();
		List list = new Vector();
		list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP1")));
		list.add(new MockAfterInvocationProvider("swap2", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP2")));
		list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP3")));
		manager.setProviders(list);
		manager.afterPropertiesSet();

		assertThat(manager.supports(new SecurityConfig("UNKNOWN_ATTRIB"))).isFalse();
		assertThat(manager.supports(new SecurityConfig("GIVE_ME_SWAP2"))).isTrue();
	}

	@Test
	public void testSupportsSecureObjectIteration() throws Exception {
		AfterInvocationProviderManager manager = new AfterInvocationProviderManager();
		List list = new Vector();
		list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP1")));
		list.add(new MockAfterInvocationProvider("swap2", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP2")));
		list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class,
				new SecurityConfig("GIVE_ME_SWAP3")));
		manager.setProviders(list);
		manager.afterPropertiesSet();

		// assertFalse(manager.supports(FilterInvocation.class));
		assertThat(manager.supports(MethodInvocation.class)).isTrue();
	}

	// ~ Inner Classes
	// ==================================================================================================

	/**
	 * Always returns the constructor-defined <code>forceReturnObject</code>, provided the
	 * same configuration attribute was provided. Also stores the secure object it
	 * supports.
	 */
	private class MockAfterInvocationProvider implements AfterInvocationProvider {

		private Class secureObject;

		private ConfigAttribute configAttribute;

		private Object forceReturnObject;

		public MockAfterInvocationProvider(Object forceReturnObject, Class secureObject,
				ConfigAttribute configAttribute) {
			this.forceReturnObject = forceReturnObject;
			this.secureObject = secureObject;
			this.configAttribute = configAttribute;
		}

		public Object decide(Authentication authentication, Object object,
				Collection<ConfigAttribute> config, Object returnedObject)
						throws AccessDeniedException {
			if (config.contains(configAttribute)) {
				return forceReturnObject;
			}

			return returnedObject;
		}

		public boolean supports(Class<?> clazz) {
			return secureObject.isAssignableFrom(clazz);
		}

		public boolean supports(ConfigAttribute attribute) {
			return attribute.equals(configAttribute);
		}
	}
}
