package common.db.dao.hibernate;

import java.io.Serializable;

import org.apache.logging.log4j.LogManager;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.PropertyValueException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.resource.transaction.spi.TransactionStatus;

import common.db.dao.DaoConstraintException;
import common.db.dao.DaoLockException;
import common.db.dao.DaoPropertyException;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;


/**
 * 汎用DAO
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public class JtaDao extends BaseDao {

	/**
	 * コンストラクタ
	 *
	 * @param config コンフィグ
	 */
	protected JtaDao(final Config config) {
		super(config);
		setNoWait(true);
	}

	/**
	 * コミット処理
	 *
	 */
	@Override
	public void commit() {
		return;
	}

	/**
	 * ロールバック処理
	 * @return 処理結果
	 */
	@Override
	public boolean rollback() {
		return false;
	}

	/**
	 * フラッシュ処理
	 *
	 * @return フラッシュされた場合 true を返す。対象なしの場合 false を返す。
	 */
	@Override
	public boolean flush() {
		try {
			getSession().flush();
			return true;
		} catch (final LockAcquisitionException ex) {
			LogManager.getLogger().info(ex.getMessage());
			throw new DaoLockException(ex, isNoWait());
		} catch (final ConstraintViolationException ex) {
			LogManager.getLogger().info(ex.getMessage());
			throw new DaoConstraintException(ex, isNoWait());
		} catch (final PropertyValueException ex) {
			// not null例外
			LogManager.getLogger().info(ex.getMessage());
			throw new DaoPropertyException(ex);
		} catch (final JDBCException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		} catch (final HibernateException ex) {
			if (!isUpdateFailed(ex)) {
				ThrowableUtil.error(ex);
				throw new PhysicalException(ex);
			}
			LogManager.getLogger().info(ex.getMessage());
			return false;
		}
	}

	/**
	 * セションクローズ
	 *
	 */
	@Override
	public void close() {
		if (0 < getSession().getStatistics().getEntityCount()) {
			getSession().clear();
		}
	}

	/**
	 * @see common.db.dao.Dao#sequence(java.lang.String)
	 */
	@Override
	public long sequence(final String name) {
		final var session = getSession();
		beginTransaction();
		try (var sw = new JtaSequenceWork(getSequenceNextValString(name))) {
			session.doWork(sw);
			return sw.getSequence();
		}
	}

	/**
	 * セッション取得
	 *
	 * @return セションオブジェクト
	 */
	@Override
	protected Session getSession() {
		return getSessionFactory().getCurrentSession();
	}

	/**
	 * トランザクション開始
	 *
	 * @return トランザクション
	 */
	@Override
	protected Transaction beginTransaction() {
		final var tran = getSession().getTransaction();
		if (!TransactionStatus.ACTIVE.equals(tran.getStatus())) {
			tran.begin();
		}
		return tran;
	}

	/**
	 * フラッシュ処理
	 *
	 */
	@Override
	protected void flushSession(final Serializable item) {
		getSession().flush();
		getSession().evict(item);
	}

	/**
	 * JTA用SequenceWork
	 * @author Tadashi Nakayama
	 */
	private static final class JtaSequenceWork extends SequenceWork implements AutoCloseable {
		/**
		 * コンストラクタ
		 * @param sql クエリ
		 */
		JtaSequenceWork(final String sql) {
			super(sql);
		}
	}
}
