/*
 * Copyright 2012-2017 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.boot.jta.bitronix;

import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;

import javax.sql.DataSource;
import javax.sql.XAConnection;
import javax.sql.XADataSource;

import bitronix.tm.resource.common.ResourceBean;
import bitronix.tm.resource.common.XAStatefulHolder;
import bitronix.tm.resource.jdbc.PoolingDataSource;

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;

/**
 * Spring friendly version of {@link PoolingDataSource}. Provides sensible defaults and
 * also supports direct wrapping of a {@link XADataSource} instance.
 *
 * @author Phillip Webb
 * @author Josh Long
 * @author Andy Wilkinson
 * @since 1.2.0
 */
@SuppressWarnings("serial")
@ConfigurationProperties(prefix = "spring.jta.bitronix.datasource")
public class PoolingDataSourceBean extends PoolingDataSource
		implements BeanNameAware, InitializingBean {

	private static final ThreadLocal<PoolingDataSourceBean> source = new ThreadLocal<>();

	private XADataSource dataSource;

	private String beanName;

	public PoolingDataSourceBean() {
		super();
		setMaxPoolSize(10);
		setAllowLocalTransactions(true);
		setEnableJdbc4ConnectionTest(true);
	}

	@Override
	public synchronized void init() {
		source.set(this);
		try {
			super.init();
		}
		finally {
			source.remove();
		}
	}

	@Override
	public void setBeanName(String name) {
		this.beanName = name;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		if (!StringUtils.hasLength(getUniqueName())) {
			setUniqueName(this.beanName);
		}
	}

	/**
	 * Set the {@link XADataSource} directly, instead of calling
	 * {@link #setClassName(String)}.
	 * @param dataSource the data source to use
	 */
	public void setDataSource(XADataSource dataSource) {
		this.dataSource = dataSource;
		setClassName(DirectXADataSource.class.getName());
		setDriverProperties(new Properties());
	}

	protected final XADataSource getDataSource() {
		return this.dataSource;
	}

	@Override
	public XAStatefulHolder createPooledConnection(Object xaFactory, ResourceBean bean)
			throws Exception {
		if (xaFactory instanceof DirectXADataSource) {
			xaFactory = ((DirectXADataSource) xaFactory).getDataSource();
		}
		return super.createPooledConnection(xaFactory, bean);
	}

	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		try {
			return ((DataSource) this).getParentLogger();
		}
		catch (Exception ex) {
			// Work around https://jira.codehaus.org/browse/BTM-134
			return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
		}
	}

	/**
	 * A {@link XADataSource} implementation that delegates to the {@link ThreadLocal}
	 * {@link PoolingDataSourceBean}.
	 *
	 * @see PoolingDataSourceBean#setDataSource(XADataSource)
	 */
	public static class DirectXADataSource implements XADataSource {

		private final XADataSource dataSource;

		public DirectXADataSource() {
			this.dataSource = source.get().dataSource;
		}

		@Override
		public PrintWriter getLogWriter() throws SQLException {
			return this.dataSource.getLogWriter();
		}

		@Override
		public XAConnection getXAConnection() throws SQLException {
			return this.dataSource.getXAConnection();
		}

		@Override
		public XAConnection getXAConnection(String user, String password)
				throws SQLException {
			return this.dataSource.getXAConnection(user, password);
		}

		@Override
		public void setLogWriter(PrintWriter out) throws SQLException {
			this.dataSource.setLogWriter(out);
		}

		@Override
		public void setLoginTimeout(int seconds) throws SQLException {
			this.dataSource.setLoginTimeout(seconds);
		}

		@Override
		public int getLoginTimeout() throws SQLException {
			return this.dataSource.getLoginTimeout();
		}

		@Override
		public Logger getParentLogger() throws SQLFeatureNotSupportedException {
			return this.dataSource.getParentLogger();
		}

		public XADataSource getDataSource() {
			return this.dataSource;
		}

	}

}
