/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.jpa.test.transaction;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;

import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
import org.hibernate.jpa.boot.spi.Bootstrap;
import org.hibernate.jpa.test.PersistenceUnitDescriptorAdapter;
import org.hibernate.jpa.test.SettingsGenerator;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
 * @author Vlad Mihalcea
 */
public class TransactionCommitFailureTest {

    public static final String COMMIT_FAILURE = "Commit failed!";

    private static final AtomicBoolean transactionFailureTrigger = new AtomicBoolean( false );

    @Test
    public void testConfiguredInterceptor() {
		Map settings = basicSettings();
		EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build();
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();
            transactionFailureTrigger.set( true );
            em.getTransaction().commit();
        }
        catch ( Exception e ) {
            assertEquals(PersistenceException.class, e.getCause().getClass());
            assertEquals(COMMIT_FAILURE, e.getCause().getMessage());
        }
        finally {
            if ( em.getTransaction() != null && em.getTransaction().isActive() ) {
                em.getTransaction().rollback();
            }
            em.close();
            emf.close();
        }
    }

    protected Map basicSettings() {
		return SettingsGenerator.generateSettings(
				Environment.HBM2DDL_AUTO, "create-drop",
				Environment.USE_NEW_ID_GENERATOR_MAPPINGS, "true",
				Environment.DIALECT, Dialect.getDialect().getClass().getName(),
                Environment.CONNECTION_PROVIDER, ProxyConnectionProvider.class.getName()
		);
    }

	public static class ProxyConnectionProvider extends DriverManagerConnectionProviderImpl {

		@Override
		public Connection getConnection() throws SQLException {
			Connection delegate = super.getConnection();
			return (Connection) Proxy.newProxyInstance(
					this.getClass().getClassLoader(),
					new Class[]{Connection.class},
					new ConnectionInvocationHandler(delegate));
		}
	}

    private static class ConnectionInvocationHandler implements InvocationHandler {

        private final Connection delegate;

        public ConnectionInvocationHandler(Connection delegate) {
            this.delegate = delegate;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if("commit".equals( method.getName() )) {
                if ( transactionFailureTrigger.get() ) {
                    throw new PersistenceException( COMMIT_FAILURE );
                }
            }
            else if("rollback".equals( method.getName() )) {
                if ( transactionFailureTrigger.get() ) {
                    transactionFailureTrigger.set( false );
                    throw new PersistenceException( "Rollback failed!" );
                }
            }
            return method.invoke(delegate, args);
        }
    }

}
