/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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 woolpack.sql.tx;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import woolpack.sql.adapter.ConnectionAdapter;
import woolpack.sql.adapter.DataSourceAdapter;

/**
 * トランザクションを制御する{@link DataSource}のプロキシと
 * トランザクションが制御された{@link DataSource}のプロキシを生成するビルダです。
 * インタープリタ デザインパターンと組み合わせることにより
 * 業務処理から RDB のトランザクション処理を分離することができます。
 * トランザクションを制御するプロキシから取得したコネクションで管理されたブロック内では、
 * トランザクションが制御されたプロキシから取得したコネクションが同一スレッド内で共有されます。
 * このクラスでは
 * {@link DataSource#getConnection()}をサポートしますが
 * {@link DataSource#getConnection(String, String)}はサポートしません。
 * このクラスは{@link ThreadLocal}を使用しています。
 * 
 * このクラスのメソッドは最初に発生した例外を再スローするため、メソッド内で続けて発生した例外の情報は失われます。
 * <br/>適用しているデザインパターン：Thread specific Storage, Proxy, Builder。
 * 
 * @author nakamura
 *
 */
public class TxBuilder {
	private DataSource dataSource;
	private ThreadLocal<TxContext> threadLocal;
	
	/**
	 * 
	 * @param dataSource RDBに接続するデータソース。
	 */
	public TxBuilder(final DataSource dataSource) {
		this.dataSource = dataSource;
		threadLocal = new ThreadLocal<TxContext>();
	}
	public DataSource getDataSource() {
		return dataSource;
	}
	public void setDataSource(final DataSource dataSource) {
		this.dataSource = dataSource;
	}

	/**
	 * {@link Connection#close()}ではコミット・ロールバックのいずれのフラグも立っていない時にコミットします。
	 * 
	 * @return トランザクションを管理するための{@link DataSource}。
	 * @see #getTxDataSource(boolean)
	 */
	public DataSource getTxDataSource() {
		return getTxDataSource(true);
	}
	
	/**
	 * トランザクションを制御する{@link DataSource}のプロキシを返します。
	 * {@link Connection#rollback()}ではロールバックのフラグを立てます。
	 * {@link Connection#commit()}ではコミットのフラグを立てます。
	 * {@link Connection#close()}では
	 * {@link #getTmpDataSource(boolean)}で作成して開いたままのリソースを全て閉じ、
	 * ロールバックのフラグによってコミットまたはロールバックを実行し、
	 * 最後に実際のコネクションを閉じます。
	 * 
	 * @param defaultCommitFlag {@link Connection#close()}でコミット・ロールバックのいずれのフラグも立っていない時にコミットするならtrue。
	 * @return トランザクションを管理するための{@link DataSource}。
	 */
	public DataSource getTxDataSource(final boolean defaultCommitFlag) {
		return new DataSourceAdapter(dataSource) {
			@Override
			public Connection getConnection() {
				final TxContext context = new TxContext();
				threadLocal.set(context);
				return new ConnectionAdapter(null) {
					@Override
					public void commit() throws SQLException {
						context.commit();
					}
					@Override
					public void rollback() throws SQLException {
						context.rollback();
					}
					@Override
					public void close() throws SQLException {
						final Connection connection = context.getConnection();
						if (connection == null) {
							return;
						}
						SQLException e0 = null;
						try {
							try {
								try {
									context.closeAll();
								} catch (final SQLException e1) {
									e0 = e1;
								} finally {
									if (context.isRollback()) {
										connection.rollback();
									} else if (defaultCommitFlag || context.isCommit()) {
										connection.commit();
									}
								}
							} catch (final SQLException e1) {
								if (e0 == null) {
									e0 = e1;
								}
							} finally {
								connection.close();
							}
						} catch (final SQLException e1) {
							if (e0 == null) {
								e0 = e1;
							}
						} finally {
							threadLocal.remove();
						}
						if (e0 != null) {
							throw e0;
						}
					}
				};
			}
		};
	}

	/**
	 * {@link DataSource#getConnection()}実行時に以前のリソースを閉じます。
	 * 
	 * @return トランザクションが管理された{@link DataSource}。
	 * @see #getTmpDataSource(boolean)
	 */
	public DataSource getTmpDataSource() {
		return getTmpDataSource(true);
	}
	
	/**
	 * トランザクションが制御された{@link DataSource}のプロキシを返します。
	 * {@link DataSource#getConnection()}では
	 * ロールバックのフラグが立っている場合は{@link SQLException}を投げ、
	 * 初めての呼び出しで実際のコネクションを作成し、
	 * 引数が true の場合は以前のリソースを閉じます。
	 * 
	 * @param refreshFlag {@link DataSource#getConnection()}実行時に以前のリソースを閉じる場合は true。
	 * @return トランザクションが管理された{@link DataSource}。
	 */
	public DataSource getTmpDataSource(final boolean refreshFlag) {
		return new DataSourceAdapter(dataSource) {
			@Override
			public Connection getConnection() throws SQLException {
				final TxContext context = threadLocal.get();
				if (context == null) {
					throw new SQLException("transaction not start.");
				}
				if (context.isRollback()) {
					throw new SQLException("already rollbacked.");
				}
				if (context.getConnection() == null) {
					context.setConnection(super.getConnection());
				}
				if (refreshFlag) {
					context.closeAll();
				}
				return context.newTmpConnection();
			}
		};
	}
}
