package com.atlassian.stash.internal.crowd;

import com.atlassian.crowd.util.persistence.hibernate.batch.AbstractBatchProcessor;
import org.hibernate.*;

import javax.annotation.Nonnull;

import static com.google.common.base.Preconditions.checkState;

/**
 * Version of {@link com.atlassian.crowd.util.persistence.hibernate.batch.BatchProcessor BatchProcessor} that integrates
 * with Spring transaction management instead of directly manipulating the Hibernate session.
 *
 * @since 1.3
 */
public class TransactionManagerBatchProcessor extends AbstractBatchProcessor<Session> {

    private final SessionFactory sessionFactory;
    private final ThreadLocal<BatchTransaction> transactionHolder;
    private final BatchTransactionSupplier transactionSupplier;

    public TransactionManagerBatchProcessor(SessionFactory sessionFactory,
                                            BatchTransactionSupplier transactionSupplier) {
        this.sessionFactory = sessionFactory;
        this.transactionSupplier = transactionSupplier;

        transactionHolder = new ThreadLocal<>();
    }

    @Override
    protected void afterProcessBatch() {
        commitTransaction();
    }

    @Override
    protected void afterProcessCollection() {
        // no-op. In AbstractBatchProcessor, a 'collection' is split up into 1 or more batches. Stash handles the
        // transactions at the batch level, not the 'collection' level.
    }

    @Override
    protected void afterProcessIndividual() {
        commitTransaction();
    }

    @Override
    protected void beforeProcessBatch() {
        startTransaction();
    }

    @Override
    protected void beforeProcessIndividual() {
        startTransaction();
    }

    @Override
    protected void beforeProcessCollection() {
        // no-op. In AbstractBatchProcessor, a 'collection' is split up into 1 or more batches. Stash handles the
        // transactions at the batch level, not the 'collection' level.
    }

    @Override
    protected Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    @Override
    protected void rollbackProcessBatch() {
        rollbackTransaction();
    }

    @Override
    protected void rollbackProcessIndividual() {
        rollbackTransaction();
    }

    private void clearTransaction() {
        transactionHolder.remove();
    }

    private void commitTransaction() {
        getTransactionOrFail().commit();
        clearTransaction();
    }

    private void rollbackTransaction() {
        getTransactionOrFail().rollback();
        clearTransaction();
    }

    private void startTransaction() {
        checkState(transactionHolder.get() == null, "No transaction should be active");
        transactionHolder.set(transactionSupplier.get());

        Session session = sessionFactory.getCurrentSession();
        session.setCacheMode(CacheMode.IGNORE);
    }

    @Nonnull
    private BatchTransaction getTransactionOrFail() {
        BatchTransaction transaction = transactionHolder.get();
        checkState(transaction != null, "A transaction should be active");

        return transaction;
    }
}
