/*
 * Copyright 2010-2013 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.data.gemfire.support;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.CoreMatchers.isA;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

import java.util.Arrays;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Matchers;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.ContextStoppedEvent;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.util.ObjectUtils;

/**
 * The SpringContextBootstrappingInitializerTest class is a test suite of test cases testing the contract
 * and functionality of the SpringContextBootstrappingInitializer class.  This test class focuses on testing isolated
 * units of functionality in the Initializer class directly, mocking any dependencies as appropriate, in order for the
 * class to uphold it's contract.
 *
 * @author John Blum
 * @see org.junit.Test
 * @see org.mockito.Mockito
 * @see org.springframework.context.ApplicationContext
 * @see org.springframework.context.ConfigurableApplicationContext
 * @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer
 * @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializerIntegrationTest
 * @since 1.4.0
 */
@SuppressWarnings("unused")
public class SpringContextBootstrappingInitializerTest {

	@Rule
	public ExpectedException expectedException = ExpectedException.none();

	@After
	public void tearDown() {
		SpringContextBootstrappingInitializer.applicationContext = null;
		SpringContextBootstrappingInitializer.contextRefreshedEvent = null;
		SpringContextBootstrappingInitializer.setBeanClassLoader(null);
		SpringContextBootstrappingInitializer.unregister(TestAppConfigOne.class);
		SpringContextBootstrappingInitializer.unregister(TestAppConfigTwo.class);
	}

	protected static Properties createParameters(final String parameter, final String value) {
		Properties parameters = new Properties();
		parameters.setProperty(parameter, value);
		return parameters;
	}

	protected static Properties createParameters(final Properties parameters, final String parameter, final String value) {
		parameters.setProperty(parameter, value);
		return parameters;
	}

	@Test
	public void getInitializedApplicationContext() {
		ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"testGetApplicationContext");

		SpringContextBootstrappingInitializer.applicationContext = mockApplicationContext;

		assertThat(SpringContextBootstrappingInitializer.getApplicationContext(),
			is(sameInstance(mockApplicationContext)));
	}

	@Test
	public void getUninitializedApplicationContext() {
		expectedException.expect(IllegalStateException.class);
		expectedException.expectMessage("A Spring ApplicationContext was not configured and initialized properly");
		expectedException.expectCause(is(nullValue(Throwable.class)));

		SpringContextBootstrappingInitializer.getApplicationContext();
	}

	@Test
	public void setBeanClassLoaderWithCurrentThreadContextClassLoader() {
		assertThat(SpringContextBootstrappingInitializer.applicationContext, is(nullValue()));
		SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader());
	}

	@Test
	public void setBeanClassLoaderWithCurrentThreadContextClassLoaderWhenApplicationContextIsInactive() {
		ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockApplicationContext");

		when(mockApplicationContext.isActive()).thenReturn(false);

		SpringContextBootstrappingInitializer.applicationContext = mockApplicationContext;
		SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader());

		verify(mockApplicationContext, times(1)).isActive();
	}

	@Test
	public void setBeanClassLoaderWithCurrentThreadContextClassLoaderWhenApplicationContextIsActive() {
		ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockApplicationContext");

		when(mockApplicationContext.isActive()).thenReturn(true);

		expectedException.expect(IllegalStateException.class);
		expectedException.expectCause(is(nullValue(Throwable.class)));
		expectedException.expectMessage("A Spring ApplicationContext has already been initialized");

		try {
			SpringContextBootstrappingInitializer.applicationContext = mockApplicationContext;
			SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader());
		}
		finally {
			verify(mockApplicationContext, times(1)).isActive();
		}
	}

	@Test
	public void createApplicationContextWhenAnnotatedClassesBasePackagesAndConfigLocationsAreUnspecified() {
		expectedException.expect(IllegalArgumentException.class);
		expectedException.expectCause(is(nullValue(Throwable.class)));
		expectedException.expectMessage("'AnnotatedClasses', 'basePackages' or 'configLocations' must be specified"
			+ " in order to construct and configure an instance of the ConfigurableApplicationContext");

		new SpringContextBootstrappingInitializer().createApplicationContext(null, null);
	}

	@Test
	public void createAnnotationApplicationContextWithAnnotatedClasses() {
		final AnnotationConfigApplicationContext mockAnnotationApplicationContext = mock(AnnotationConfigApplicationContext.class,
			"MockAnnotationApplicationContext");

		final ConfigurableApplicationContext mockXmlApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockXmlApplicationContext");

		Class<?>[] annotatedClasses = { TestAppConfigOne.class, TestAppConfigTwo.class };

		SpringContextBootstrappingInitializer.register(annotatedClasses[0]);
		SpringContextBootstrappingInitializer.register(annotatedClasses[1]);

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() {
			@Override ConfigurableApplicationContext createApplicationContext(final String[] configLocations) {
				return (ObjectUtils.isEmpty(configLocations) ? mockAnnotationApplicationContext
					: mockXmlApplicationContext);
			}
		};

		ConfigurableApplicationContext actualApplicationContext = initializer.createApplicationContext(null, null);

		assertThat(actualApplicationContext,
			is(sameInstance((ConfigurableApplicationContext) mockAnnotationApplicationContext)));

		verify(mockAnnotationApplicationContext, times(1)).register(annotatedClasses[0], annotatedClasses[1]);
	}

	@Test
	public void createAnnotationApplicationContextWithBasePackages() {
		final AnnotationConfigApplicationContext mockAnnotationApplicationContext = mock(AnnotationConfigApplicationContext.class,
			"MockAnnotationApplicationContext");

		final ConfigurableApplicationContext mockXmlApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockXmlApplicationContext");

		String[] basePackages = { "org.example.app" };

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() {
			@Override ConfigurableApplicationContext createApplicationContext(final String[] configLocations) {
				return (ObjectUtils.isEmpty(configLocations) ? mockAnnotationApplicationContext
					: mockXmlApplicationContext);
			}
		};

		ConfigurableApplicationContext actualApplicationContext = initializer.createApplicationContext(basePackages, null);

		assertThat(actualApplicationContext,
			is(sameInstance((ConfigurableApplicationContext) mockAnnotationApplicationContext)));

		verify(mockAnnotationApplicationContext, times(1)).scan(eq(basePackages[0]));
	}

	@Test
	public void createXmlApplicationContext() {
		final ConfigurableApplicationContext mockAnnotationApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockAnnotationApplicationContext");

		final ConfigurableApplicationContext mockXmlApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockXmlApplicationContext");

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() {
			@Override ConfigurableApplicationContext createApplicationContext(final String[] configLocations) {
				return (ObjectUtils.isEmpty(configLocations) ? mockAnnotationApplicationContext
					: mockXmlApplicationContext);
			}
		};

		ConfigurableApplicationContext actualApplicationContext = initializer.createApplicationContext(null,
			new String[] { "/path/to/application/context.xml" });

		assertThat(actualApplicationContext, is(sameInstance(mockXmlApplicationContext)));
	}

	@Test
	public void initApplicationContext() {
		AbstractApplicationContext mockApplicationContext = mock(AbstractApplicationContext.class,
			"MockApplicationContext");

		SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader());

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer();

		assertSame(mockApplicationContext, initializer.initApplicationContext(mockApplicationContext));

		verify(mockApplicationContext, times(1)).addApplicationListener(same(initializer));
		verify(mockApplicationContext, times(1)).registerShutdownHook();
		verify(mockApplicationContext, times(1)).setClassLoader(eq(Thread.currentThread().getContextClassLoader()));
	}

	@Test
	public void initApplicationContextWithNull() {
		expectedException.expect(IllegalArgumentException.class);
		expectedException.expectCause(is(nullValue(Throwable.class)));
		expectedException.expectMessage("ConfigurableApplicationContext must not be null");

		new SpringContextBootstrappingInitializer().initApplicationContext(null);
	}

	@Test
	public void refreshApplicationContext() {
		ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockApplicationContext");

		assertThat(new SpringContextBootstrappingInitializer().refreshApplicationContext(mockApplicationContext),
			is(sameInstance(mockApplicationContext)));

		verify(mockApplicationContext, times(1)).refresh();
	}

	@Test
	public void refreshApplicationContextWithNull() {
		expectedException.expect(IllegalArgumentException.class);
		expectedException.expectCause(is(nullValue(Throwable.class)));
		expectedException.expectMessage("ConfigurableApplicationContext must not be null");

		new SpringContextBootstrappingInitializer().refreshApplicationContext(null);
	}

	@Test
	public void registerAnnotatedClasses() {
		AnnotationConfigApplicationContext mockApplicationContext = mock(AnnotationConfigApplicationContext.class,
			"MockApplicationContext");

		Class<?>[] annotatedClasses = { TestAppConfigOne.class, TestAppConfigTwo.class };

		assertThat(new SpringContextBootstrappingInitializer()
				.registerAnnotatedClasses(mockApplicationContext, annotatedClasses),
			is(sameInstance(mockApplicationContext)));

		verify(mockApplicationContext, times(1)).register(annotatedClasses);
	}

	@Test
	public void registerAnnotatedClassesWithEmptyAnnotatedClassesArray() {
		AnnotationConfigApplicationContext mockApplicationContext = mock(AnnotationConfigApplicationContext.class,
			"MockApplicationContext");

		assertThat(new SpringContextBootstrappingInitializer().registerAnnotatedClasses(mockApplicationContext,
				new Class<?>[0]),
			is(sameInstance(mockApplicationContext)));

		verify(mockApplicationContext, never()).register(any(Class[].class));
	}

	@Test
	public void registerAnnotatedClassesWithNonAnnotationBasedApplicationContext() {
		ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockApplicationContext");

		assertThat(new SpringContextBootstrappingInitializer().registerAnnotatedClasses(mockApplicationContext,
			new Class<?>[] { TestAppConfigOne.class }), is(sameInstance(mockApplicationContext)));
	}

	@Test
	public void scanBasePackages() {
		AnnotationConfigApplicationContext mockApplicationContext = mock(AnnotationConfigApplicationContext.class,
			"MockApplicationContext");

		String[] basePackages = { "org.example.app", "org.example.plugins" };

		assertThat(new SpringContextBootstrappingInitializer().scanBasePackages(mockApplicationContext, basePackages),
			is(sameInstance(mockApplicationContext)));

		verify(mockApplicationContext, times(1)).scan(basePackages);
	}

	@Test
	public void scanBasePackagesWithEmptyBasePackagesArray() {
		AnnotationConfigApplicationContext mockApplicationContext = mock(AnnotationConfigApplicationContext.class,
			"MockApplicationContext");

		assertThat(new SpringContextBootstrappingInitializer().scanBasePackages(mockApplicationContext, null),
			is(sameInstance(mockApplicationContext)));

		verify(mockApplicationContext, never()).scan(any(String[].class));
	}

	@Test
	public void scanBasePackagesWithNonAnnotationBasedApplicationContext() {
		ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockApplicationContext");

		assertThat(new SpringContextBootstrappingInitializer().scanBasePackages(mockApplicationContext,
			new String[] { "org.example.app" }), is(sameInstance(mockApplicationContext)));
	}

	@Test
	public void setClassLoader() {
		AbstractApplicationContext mockApplicationContext = mock(AbstractApplicationContext.class,
			"MockApplicationContext");

		SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader());

		assertThat(new SpringContextBootstrappingInitializer().setClassLoader(mockApplicationContext),
			is(sameInstance(mockApplicationContext)));

		verify(mockApplicationContext, times(1)).setClassLoader(eq(Thread.currentThread().getContextClassLoader()));
	}

	@Test
	public void setClassLoaderWithNonSettableClassLoaderApplicationContext() {
		ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"MockApplicationContext");

		SpringContextBootstrappingInitializer.setBeanClassLoader(Thread.currentThread().getContextClassLoader());

		assertThat(new SpringContextBootstrappingInitializer().setClassLoader(mockApplicationContext),
			is(sameInstance(mockApplicationContext)));
	}

	@Test
	public void setClassLoaderWithNullClassLoader() {
		AbstractApplicationContext mockApplicationContext = mock(AbstractApplicationContext.class,
			"MockApplicationContext");

		SpringContextBootstrappingInitializer.setBeanClassLoader(null);

		assertThat(new SpringContextBootstrappingInitializer().setClassLoader(mockApplicationContext),
			is(sameInstance(mockApplicationContext)));

		verify(mockApplicationContext, never()).setClassLoader(any(ClassLoader.class));
	}

	private Class<?>[] annotatedClasses(final Class<?>... annotatedClasses) {
		return argThat(argument -> {
			assertThat(argument instanceof Class<?>[], is(true));
			return Arrays.equals(annotatedClasses, (Class<?>[]) argument);
		});
	}

	@Test
	public void nullSafeGetApplicationContextIdWithNullReference() {
		assertThat(new SpringContextBootstrappingInitializer().nullSafeGetApplicationContextId(null), is(nullValue()));
	}

	@Test
	public void nullSafeGetApplicationContextIdWithNonNullReference() {
		ApplicationContext mockApplicationContext = mock(ApplicationContext.class, "MockApplicationContext");

		when(mockApplicationContext.getId()).thenReturn("123");

		assertThat(new SpringContextBootstrappingInitializer().nullSafeGetApplicationContextId(mockApplicationContext),
			is(equalTo("123")));
	}

	@Test
	public void testInitWithAnnotatedClasses() {
		final AnnotationConfigApplicationContext mockApplicationContext = mock(AnnotationConfigApplicationContext.class,
			"testInitWithAnnotatedClasses");

		doNothing().when(mockApplicationContext).addApplicationListener(any(ApplicationListener.class));
		doNothing().when(mockApplicationContext).registerShutdownHook();
		doNothing().when(mockApplicationContext).refresh();
		doNothing().when(mockApplicationContext).register(Matchers.<Class<?>[]>anyVararg());
		//doNothing().when(mockApplicationContext).register(annotatedClasses(TestAppConfigOne.class, TestAppConfigTwo.class));

		when(mockApplicationContext.getId()).thenReturn("testInitWithAnnotatedClasses");
		when(mockApplicationContext.isRunning()).thenReturn(true);

		assertNull(SpringContextBootstrappingInitializer.applicationContext);

		SpringContextBootstrappingInitializer.register(TestAppConfigOne.class);
		SpringContextBootstrappingInitializer.register(TestAppConfigTwo.class);

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() {
			@Override protected ConfigurableApplicationContext createApplicationContext(String[] configLocations) {
				return mockApplicationContext;
			}
		};

		initializer.init(createParameters("test", "test"));

		verify(mockApplicationContext, times(1)).addApplicationListener(same(initializer));
		verify(mockApplicationContext, times(1)).registerShutdownHook();
		verify(mockApplicationContext, times(1)).register(TestAppConfigOne.class, TestAppConfigTwo.class);
		//verify(mockApplicationContext, times(1)).register(annotatedClasses(TestAppConfigOne.class, TestAppConfigTwo.class));
		//verify(mockApplicationContext, times(1)).register(Matchers.<Class<?>[]>anyVararg());
		verify(mockApplicationContext, never()).scan(any(String[].class));

		assertEquals(mockApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext());
	}

	@Test
	public void testInitWithExistingApplicationContext() {
		ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"testInitWithExistingApplicationContext");

		when(mockApplicationContext.isActive()).thenReturn(true);
		when(mockApplicationContext.getId()).thenReturn("testInitWithExistingApplicationContext");

		SpringContextBootstrappingInitializer.applicationContext = mockApplicationContext;

		assertSame(mockApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext());

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer();

		initializer.init(createParameters("test", "test"));

		verify(mockApplicationContext, never()).addApplicationListener(any(SpringContextBootstrappingInitializer.class));
		verify(mockApplicationContext, never()).registerShutdownHook();
		verify(mockApplicationContext, never()).refresh();

		assertSame(mockApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext());
	}

	@Test
	public void testInitWhenApplicationContextIsNull() {
		assertNull(SpringContextBootstrappingInitializer.applicationContext);

		final ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"testInitWhenApplicationContextIsNull");

		when(mockApplicationContext.getId()).thenReturn("testInitWhenApplicationContextIsNull");
		when(mockApplicationContext.isRunning()).thenReturn(true);

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() {
			@Override
			protected ConfigurableApplicationContext createApplicationContext(final String[] basePackages,
					final String[] configLocations) {
				return mockApplicationContext;
			}
		};

		initializer.init(createParameters(SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER,
			"/path/to/spring/application/context.xml"));

		verify(mockApplicationContext, times(1)).addApplicationListener(same(initializer));
		verify(mockApplicationContext, times(1)).registerShutdownHook();
		verify(mockApplicationContext, times(1)).refresh();

		assertSame(mockApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext());
	}

	@Test
	public void testInitWhenApplicationContextIsInactive() {
		ConfigurableApplicationContext mockInactiveApplicationContext = mock(ConfigurableApplicationContext.class,
			"testInitWhenApplicationContextIsInactive.Inactive");

		when(mockInactiveApplicationContext.isActive()).thenReturn(false);

		SpringContextBootstrappingInitializer.applicationContext = mockInactiveApplicationContext;

		assertSame(mockInactiveApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext());

		final ConfigurableApplicationContext mockNewApplicationContext = mock(ConfigurableApplicationContext.class,
			"testInitWhenApplicationContextIsInactive.New");

		when(mockNewApplicationContext.getId()).thenReturn("testInitWhenApplicationContextIsInactive.New");
		when(mockNewApplicationContext.isRunning()).thenReturn(true);

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() {
			@Override
			protected ConfigurableApplicationContext createApplicationContext(final String[] basePackages,
					final String[] configLocations) {
				return mockNewApplicationContext;
			}
		};

		initializer.init(createParameters(SpringContextBootstrappingInitializer.BASE_PACKAGES_PARAMETER,
			"org.example.app"));

		verify(mockNewApplicationContext, times(1)).addApplicationListener(same(initializer));
		verify(mockNewApplicationContext, times(1)).registerShutdownHook();
		verify(mockNewApplicationContext, times(1)).refresh();

		assertSame(mockNewApplicationContext, SpringContextBootstrappingInitializer.getApplicationContext());
	}

	@Test
	public void testInitWhenBasePackagesAndContextConfigLocationsParametersAreUnspecified() throws Throwable {
		assertThat(SpringContextBootstrappingInitializer.applicationContext, is(nullValue()));

		expectedException.expect(ApplicationContextException.class);
		expectedException.expectCause(isA(IllegalArgumentException.class));
		expectedException.expectMessage(containsString("Failed to bootstrap the Spring ApplicationContext"));

		new SpringContextBootstrappingInitializer().init(createParameters(createParameters(
				SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER, ""),
			SpringContextBootstrappingInitializer.BASE_PACKAGES_PARAMETER, "  "));
	}

	@Test(expected = IllegalStateException.class)
	public void testInitWhenApplicationContextIsNotRunning() {
		assertNull(SpringContextBootstrappingInitializer.applicationContext);

		final ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"testInitWhenApplicationContextIsNotRunning");

		when(mockApplicationContext.getId()).thenReturn("testInitWhenApplicationContextIsNotRunning");
		when(mockApplicationContext.isRunning()).thenReturn(false);

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() {
			@Override
			protected ConfigurableApplicationContext createApplicationContext(final String[] basePackages,
					final String[] configLocations) {
				return mockApplicationContext;
			}
		};

		try {
			initializer.init(createParameters(SpringContextBootstrappingInitializer.BASE_PACKAGES_PARAMETER,
				"org.example.app, org.example.plugins"));

			SpringContextBootstrappingInitializer.getApplicationContext();
		}
		catch (ApplicationContextException expected) {
			assertTrue(expected.getMessage().contains("Failed to bootstrap the Spring ApplicationContext"));
			assertTrue(expected.getCause() instanceof IllegalStateException);
			assertEquals("The Spring ApplicationContext (testInitWhenApplicationContextIsNotRunning) failed to be properly initialized with the context config files ([]) or base packages ([org.example.app, org.example.plugins])!",
				expected.getCause().getMessage());
			throw (IllegalStateException) expected.getCause();
		}
		finally {
			verify(mockApplicationContext, times(1)).addApplicationListener(same(initializer));
			verify(mockApplicationContext, times(1)).registerShutdownHook();
			verify(mockApplicationContext, times(1)).refresh();

			assertNull(SpringContextBootstrappingInitializer.applicationContext);
		}
	}

	@Test(expected = IllegalStateException.class)
	public void testInitLogsErrors() throws Throwable {
		final Log mockLog = mock(Log.class, "testInitLogsErrors.MockLog");

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer() {
			@Override protected Log initLogger() {
				return mockLog;
			}

			@Override protected ConfigurableApplicationContext createApplicationContext(String[] basePackages,
					String[] configLocations) {

				throw new IllegalStateException("TEST");
			}
		};

		try {
			initializer.init(createParameters(SpringContextBootstrappingInitializer.CONTEXT_CONFIG_LOCATIONS_PARAMETER,
				"classpath/to/spring/application/context.xml"));
		}
		catch (ApplicationContextException expected) {
			assertTrue(expected.getMessage().contains("Failed to bootstrap the Spring ApplicationContext"));
			assertTrue(expected.getCause() instanceof IllegalStateException);
			assertEquals("TEST", expected.getCause().getMessage());
			throw expected.getCause();
		}
		finally {
			verify(mockLog, times(1)).error(eq("Failed to bootstrap the Spring ApplicationContext"),
				any(RuntimeException.class));
		}
	}

	protected static void assertNotified(TestApplicationListener listener, ApplicationContextEvent expectedEvent) {
		assertThat(listener, is(notNullValue()));
		assertThat(listener.isNotified(), is(true));
		assertThat(listener.getActualEvent(), is(sameInstance(expectedEvent)));
	}

	protected static void assertUnnotified(TestApplicationListener listener) {
		assertThat(listener, is(notNullValue()));
		assertThat(listener.isNotified(), is(false));
		assertThat(listener.getActualEvent(), is(nullValue()));
	}

	@Test
	public void onContextClosedApplicationEvent() {
		TestApplicationListener testApplicationListener = new TestApplicationListener(
			"testOnContextClosedApplicationEvent");

		try {
			testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener);

			assertUnnotified(testApplicationListener);

			SpringContextBootstrappingInitializer.contextRefreshedEvent = mock(ContextRefreshedEvent.class,
				"MockContextRefreshedEvent");

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent, isA(ContextRefreshedEvent.class));

			new SpringContextBootstrappingInitializer().onApplicationEvent(mock(ContextClosedEvent.class,
				"MockContextClosedEvent"));

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent, is(nullValue()));
			assertUnnotified(testApplicationListener);
		}
		finally {
			SpringContextBootstrappingInitializer.unregister(testApplicationListener);
		}
	}

	@Test
	public void onContextRefreshedApplicationEvent() {
		TestApplicationListener testApplicationListener = new TestApplicationListener(
			"testOnContextRefreshedApplicationEvent");

		try {
			testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener);

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent, is(nullValue()));
			assertUnnotified(testApplicationListener);

			ContextRefreshedEvent mockContextRefreshedEvent = mock(ContextRefreshedEvent.class,
				"MockContextRefreshedEvent");

			new SpringContextBootstrappingInitializer().onApplicationEvent(mockContextRefreshedEvent);

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent, is(sameInstance(mockContextRefreshedEvent)));
			assertNotified(testApplicationListener, mockContextRefreshedEvent);
		}
		finally {
			SpringContextBootstrappingInitializer.unregister(testApplicationListener);
		}
	}

	@Test
	public void onContextStartedApplicationEvent() {
		TestApplicationListener testApplicationListener = new TestApplicationListener(
			"testOnContextStartedApplicationEvent");

		try {
			testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener);

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent, is(nullValue()));
			assertUnnotified(testApplicationListener);

			new SpringContextBootstrappingInitializer().onApplicationEvent(mock(ContextStartedEvent.class,
				"MockContextStartedEvent"));

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent, is(nullValue()));
			assertUnnotified(testApplicationListener);
		}
		finally {
			SpringContextBootstrappingInitializer.unregister(testApplicationListener);
		}
	}

	@Test
	public void onContextStoppedApplicationEvent() {
		TestApplicationListener testApplicationListener = new TestApplicationListener(
			"testOnContextStartedApplicationEvent");

		try {
			testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener);

			assertUnnotified(testApplicationListener);

			ContextRefreshedEvent mockContextRefreshedEvent = mock(ContextRefreshedEvent.class,
				"MockContextRefreshedEvent");

			SpringContextBootstrappingInitializer.contextRefreshedEvent = mockContextRefreshedEvent;

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent, is(sameInstance(
				mockContextRefreshedEvent)));

			new SpringContextBootstrappingInitializer().onApplicationEvent(mock(ContextStoppedEvent.class,
				"MockContextStoppedEvent"));

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent, is(sameInstance(mockContextRefreshedEvent)));
			assertUnnotified(testApplicationListener);
		}
		finally {
			SpringContextBootstrappingInitializer.unregister(testApplicationListener);
		}
	}

	@Test
	public void onApplicationEventWithMultipleRegisteredApplicationListeners() {
		TestApplicationListener testApplicationListenerOne = new TestApplicationListener("TestApplicationListener.1");

		TestApplicationListener testApplicationListenerTwo = new TestApplicationListener("TestApplicationListener.2");

		TestApplicationListener testApplicationListenerThree = new TestApplicationListener("TestApplicationListener.3");

		try {
			testApplicationListenerOne = SpringContextBootstrappingInitializer.register(testApplicationListenerOne);
			testApplicationListenerTwo = SpringContextBootstrappingInitializer.register(testApplicationListenerTwo);
			testApplicationListenerThree = SpringContextBootstrappingInitializer.register(testApplicationListenerThree);

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent, is(nullValue()));
			assertUnnotified(testApplicationListenerOne);
			assertUnnotified(testApplicationListenerTwo);
			assertUnnotified(testApplicationListenerThree);

			ContextRefreshedEvent mockContextRefreshedEvent = mock(ContextRefreshedEvent.class,
				"MockContextRefreshedEvent");

			new SpringContextBootstrappingInitializer().onApplicationEvent(mockContextRefreshedEvent);

			assertThat(SpringContextBootstrappingInitializer.contextRefreshedEvent,
				is(sameInstance(mockContextRefreshedEvent)));
			assertNotified(testApplicationListenerOne, mockContextRefreshedEvent);
			assertNotified(testApplicationListenerTwo, mockContextRefreshedEvent);
			assertNotified(testApplicationListenerThree, mockContextRefreshedEvent);
		}
		finally {
			SpringContextBootstrappingInitializer.unregister(testApplicationListenerOne);
			SpringContextBootstrappingInitializer.unregister(testApplicationListenerTwo);
			SpringContextBootstrappingInitializer.unregister(testApplicationListenerThree);
		}
	}

	@Test
	public void onApplicationEventWithNoRegisteredApplicationListener() {
		TestApplicationListener testApplicationListener = new TestApplicationListener("TestApplicationListener");

		try {
			testApplicationListener = SpringContextBootstrappingInitializer.unregister(
				SpringContextBootstrappingInitializer.register(testApplicationListener));

			assertUnnotified(testApplicationListener);

			new SpringContextBootstrappingInitializer().onApplicationEvent(mock(ContextRefreshedEvent.class,
				"MockContextRefreshedEvent"));

			assertUnnotified(testApplicationListener);
		}
		finally {
			SpringContextBootstrappingInitializer.unregister(testApplicationListener);
		}
	}

	@Test
	public void testNotifyOnExistingContextRefreshedEventBeforeApplicationContextExists() {
		assertNull(SpringContextBootstrappingInitializer.contextRefreshedEvent);

		TestApplicationListener testApplicationListener = new TestApplicationListener(
			"testNotifyOnExistingContextRefreshedEventBeforeApplicationContextExists");

		try {
			testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener);
			assertUnnotified(testApplicationListener);
		}
		finally {
			SpringContextBootstrappingInitializer.unregister(testApplicationListener);
		}
	}

	@Test
	public void testNotifyOnExistingContextRefreshedEventAfterContextRefreshed() {
		ContextRefreshedEvent testContextRefreshedEvent = new ContextRefreshedEvent(mock(ApplicationContext.class));

		new SpringContextBootstrappingInitializer().onApplicationEvent(testContextRefreshedEvent);

		TestApplicationListener testApplicationListener = new TestApplicationListener(
			"testNotifyOnExistingContextRefreshedEventAfterContextRefreshed");

		try {
			testApplicationListener = SpringContextBootstrappingInitializer.register(testApplicationListener);
			assertNotified(testApplicationListener, testContextRefreshedEvent);
		}
		finally {
			SpringContextBootstrappingInitializer.unregister(testApplicationListener);
   		}
	}

	@Test
	public void testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent() {
		ConfigurableApplicationContext mockApplicationContext = mock(ConfigurableApplicationContext.class,
			"testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent");

		SpringContextBootstrappingInitializer initializer = new SpringContextBootstrappingInitializer();

		TestApplicationListener testApplicationListenerOne = new TestApplicationListener(
			"testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent.1");

		TestApplicationListener testApplicationListenerTwo = new TestApplicationListener(
			"testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent.2");

		TestApplicationListener testApplicationListenerThree = new TestApplicationListener(
			"testOnApplicationEventAndNotifyOnExistingContextRefreshedEvent.3");

		try {
			testApplicationListenerOne = SpringContextBootstrappingInitializer.register(testApplicationListenerOne);

			assertUnnotified(testApplicationListenerOne);
			assertUnnotified(testApplicationListenerTwo);
			assertUnnotified(testApplicationListenerThree);

			ContextRefreshedEvent testContextRefreshedEvent = new ContextRefreshedEvent(mockApplicationContext);

			initializer.onApplicationEvent(testContextRefreshedEvent);

			assertNotified(testApplicationListenerOne, testContextRefreshedEvent);
			assertUnnotified(testApplicationListenerTwo);
			assertUnnotified(testApplicationListenerThree);

			testApplicationListenerTwo = SpringContextBootstrappingInitializer.register(testApplicationListenerTwo);

			assertNotified(testApplicationListenerTwo, testContextRefreshedEvent);
			assertUnnotified(testApplicationListenerOne);
			assertUnnotified(testApplicationListenerThree);

			ContextStoppedEvent testContextStoppedEvent = new ContextStoppedEvent(mockApplicationContext);

			initializer.onApplicationEvent(testContextStoppedEvent);

			assertUnnotified(testApplicationListenerOne);
			assertUnnotified(testApplicationListenerTwo);
			assertUnnotified(testApplicationListenerThree);

			initializer.onApplicationEvent(testContextRefreshedEvent);

			assertNotified(testApplicationListenerOne, testContextRefreshedEvent);
			assertNotified(testApplicationListenerTwo, testContextRefreshedEvent);
			assertUnnotified(testApplicationListenerThree);

			ContextClosedEvent testContextClosedEvent = new ContextClosedEvent(mockApplicationContext);

			initializer.onApplicationEvent(testContextClosedEvent);

			assertUnnotified(testApplicationListenerOne);
			assertUnnotified(testApplicationListenerTwo);
			assertUnnotified(testApplicationListenerThree);

			SpringContextBootstrappingInitializer.register(testApplicationListenerThree);

			assertUnnotified(testApplicationListenerOne);
			assertUnnotified(testApplicationListenerTwo);
			assertUnnotified(testApplicationListenerThree);
		}
		finally {
			SpringContextBootstrappingInitializer.unregister(testApplicationListenerOne);
			SpringContextBootstrappingInitializer.unregister(testApplicationListenerTwo);
			SpringContextBootstrappingInitializer.unregister(testApplicationListenerThree);
		}
	}

	@Configuration
	protected static class TestAppConfigOne {
	}

	@Configuration
	protected static class TestAppConfigTwo {
	}

	// TODO add additional multi-threaded test cases once MultithreadedTC test framework is added to the SDP project
	// in order to properly test concurrency of notification and registration during Spring ApplicationContext creation.

	protected static class TestApplicationListener implements ApplicationListener<ContextRefreshedEvent> {

		private volatile boolean notified = false;

		private volatile ApplicationContextEvent actualEvent;

		private final String name;

		public TestApplicationListener(final String name) {
			this.name = name;
		}

		public ApplicationContextEvent getActualEvent() {
			ApplicationContextEvent localActualEvent = this.actualEvent;
			this.actualEvent = null;
			return localActualEvent;
		}

		public boolean isNotified() {
			boolean localNotified = this.notified;
			this.notified = false;
			return localNotified;
		}

		@Override
		public void onApplicationEvent(final ContextRefreshedEvent event) {
			this.actualEvent = event;
			this.notified = true;
		}

		@Override
		public String toString() {
			return this.name;
		}
	}
}
