/*
 * Copyright 2002-2014 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.jdbc.config;

import javax.sql.DataSource;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryBean;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

/**
 * @author Dave Syer
 * @author Juergen Hoeller
 * @author Chris Beams
 * @author Sam Brannen
 */
public class JdbcNamespaceIntegrationTests {

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


	@Test
	public void createEmbeddedDatabase() throws Exception {
		Assume.group(TestGroup.LONG_RUNNING);
		assertCorrectSetup("jdbc-config.xml", "dataSource", "h2DataSource", "derbyDataSource");
	}

	@Test
	public void createEmbeddedDatabaseAgain() throws Exception {
		// If Derby isn't cleaned up properly this will fail...
		Assume.group(TestGroup.LONG_RUNNING);
		assertCorrectSetup("jdbc-config.xml", "derbyDataSource");
	}

	@Test
	public void createWithResourcePattern() throws Exception {
		assertCorrectSetup("jdbc-config-pattern.xml", "dataSource");
	}

	@Test
	public void createWithEndings() throws Exception {
		assertCorrectSetupAndCloseContext("jdbc-initialize-endings-config.xml", 2, "dataSource");
	}

	@Test
	public void createWithEndingsNested() throws Exception {
		assertCorrectSetupAndCloseContext("jdbc-initialize-endings-nested-config.xml", 2, "dataSource");
	}

	@Test
	public void createAndDestroy() throws Exception {
		ClassPathXmlApplicationContext context = context("jdbc-destroy-config.xml");
		try {
			DataSource dataSource = context.getBean(DataSource.class);
			JdbcTemplate template = new JdbcTemplate(dataSource);
			assertNumRowsInTestTable(template, 1);
			context.getBean(DataSourceInitializer.class).destroy();
			expected.expect(BadSqlGrammarException.class); // Table has been dropped
			assertNumRowsInTestTable(template, 1);
		}
		finally {
			context.close();
		}
	}

	@Test
	public void createAndDestroyNestedWithHsql() throws Exception {
		ClassPathXmlApplicationContext context = context("jdbc-destroy-nested-config.xml");
		try {
			DataSource dataSource = context.getBean(DataSource.class);
			JdbcTemplate template = new JdbcTemplate(dataSource);
			assertNumRowsInTestTable(template, 1);
			context.getBean(EmbeddedDatabaseFactoryBean.class).destroy();
			expected.expect(BadSqlGrammarException.class); // Table has been dropped
			assertNumRowsInTestTable(template, 1);
		}
		finally {
			context.close();
		}
	}

	@Test
	public void createAndDestroyNestedWithH2() throws Exception {
		ClassPathXmlApplicationContext context = context("jdbc-destroy-nested-config-h2.xml");
		try {
			DataSource dataSource = context.getBean(DataSource.class);
			JdbcTemplate template = new JdbcTemplate(dataSource);
			assertNumRowsInTestTable(template, 1);
			context.getBean(EmbeddedDatabaseFactoryBean.class).destroy();
			expected.expect(BadSqlGrammarException.class); // Table has been dropped
			assertNumRowsInTestTable(template, 1);
		}
		finally {
			context.close();
		}
	}

	@Test
	public void multipleDataSourcesHaveDifferentDatabaseNames() throws Exception {
		DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
		new XmlBeanDefinitionReader(factory).loadBeanDefinitions(new ClassPathResource(
			"jdbc-config-multiple-datasources.xml", getClass()));
		assertBeanPropertyValueOf("databaseName", "firstDataSource", factory);
		assertBeanPropertyValueOf("databaseName", "secondDataSource", factory);
	}

	private ClassPathXmlApplicationContext context(String file) {
		return new ClassPathXmlApplicationContext(file, getClass());
	}

	private void assertBeanPropertyValueOf(String propertyName, String expected, DefaultListableBeanFactory factory) {
		BeanDefinition bean = factory.getBeanDefinition(expected);
		PropertyValue value = bean.getPropertyValues().getPropertyValue(propertyName);
		assertThat(value, is(notNullValue()));
		assertThat(value.getValue().toString(), is(expected));
	}

	private void assertNumRowsInTestTable(JdbcTemplate template, int count) {
		assertEquals(count, template.queryForObject("select count(*) from T_TEST", Integer.class).intValue());
	}

	private void assertCorrectSetup(String file, String... dataSources) {
		assertCorrectSetupAndCloseContext(file, 1, dataSources);
	}

	private void assertCorrectSetupAndCloseContext(String file, int count, String... dataSources) {
		ConfigurableApplicationContext context = context(file);
		try {
			for (String dataSourceName : dataSources) {
				DataSource dataSource = context.getBean(dataSourceName, DataSource.class);
				JdbcTemplate template = new JdbcTemplate(dataSource);
				assertNumRowsInTestTable(template, count);
			}
		}
		finally {
			context.close();
		}
	}

}
