/*
 * 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.engine.transaction.jta.platform.internal;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import javax.transaction.xa.XAResource;

import org.hibernate.HibernateException;

/**
 * JTA platform implementation intended for use with WebSphere Application Server (WAS).
 * <p/>
 * WAS, unlike every other app server on the planet, does not allow direct access to the JTS TransactionManager.
 * Instead, for common transaction-related tasks users must utilize a proprietary API known as ExtendedJTATransaction.
 * <p/>
 * Even more unfortunate, the exact TransactionManagerLookup to use inside of WAS is highly dependent upon<ul>
 *     <li>WAS version</li>
 *     <li>the WAS container in which Hibernate will be utilized</li>
 * </ul>
 * <p/>
 * This class is reported to work on WAS version 6 in any of the standard J2EE/JEE component containers.
 *
 * @author Gavin King
 * @author <a href="mailto:jesper@udby.com>Jesper Udby</a>
 * @author Steve Ebersole
 */
public class WebSphereExtendedJtaPlatform extends AbstractJtaPlatform {
	public static final String UT_NAME = "java:comp/UserTransaction";

	@Override
	protected boolean canCacheTransactionManager() {
		return true;
	}

	@Override
	protected TransactionManager locateTransactionManager() {
		return new TransactionManagerAdapter();
	}

	@Override
	protected UserTransaction locateUserTransaction() {
		return (UserTransaction) jndiService().locate( UT_NAME );
	}

	@Override
	public Object getTransactionIdentifier(Transaction transaction) {
		// WebSphere, however, is not a sane JEE/JTA container...
		return transaction.hashCode();
	}

	public class TransactionManagerAdapter implements TransactionManager {
		private final Class synchronizationCallbackClass;
		private final Method registerSynchronizationMethod;
		private final Method getLocalIdMethod;
		private Object extendedJTATransaction;

		private TransactionManagerAdapter() throws HibernateException {
			try {
				synchronizationCallbackClass = Class.forName( "com.ibm.websphere.jtaextensions.SynchronizationCallback" );
				Class extendedJTATransactionClass = Class.forName( "com.ibm.websphere.jtaextensions.ExtendedJTATransaction" );
				registerSynchronizationMethod = extendedJTATransactionClass.getMethod(
						"registerSynchronizationCallbackForCurrentTran",
						new Class[] { synchronizationCallbackClass }
				);
				getLocalIdMethod = extendedJTATransactionClass.getMethod( "getLocalId", (Class[]) null );
			}
			catch ( ClassNotFoundException cnfe ) {
				throw new HibernateException( cnfe );
			}
			catch ( NoSuchMethodException nsme ) {
				throw new HibernateException( nsme );
			}
		}

		@Override
		public void begin() throws NotSupportedException, SystemException {
			throw new UnsupportedOperationException();
		}

		@Override
		public void commit() throws UnsupportedOperationException {
			throw new UnsupportedOperationException();
		}

		@Override
		public int getStatus() throws SystemException {
			return getTransaction() == null ? Status.STATUS_NO_TRANSACTION : getTransaction().getStatus();
		}

		@Override
		public Transaction getTransaction() throws SystemException {
			return new TransactionAdapter();
		}

		@Override
		public void resume(Transaction txn) throws UnsupportedOperationException {
			throw new UnsupportedOperationException();
		}

		@Override
		public void rollback() throws UnsupportedOperationException {
			throw new UnsupportedOperationException();
		}

		@Override
		public void setRollbackOnly() throws UnsupportedOperationException {
			throw new UnsupportedOperationException();
		}

		@Override
		public void setTransactionTimeout(int i) throws UnsupportedOperationException {
			throw new UnsupportedOperationException();
		}

		@Override
		public Transaction suspend() throws UnsupportedOperationException {
			throw new UnsupportedOperationException();
		}

		public class TransactionAdapter implements Transaction {

			private TransactionAdapter() {
				if ( extendedJTATransaction == null ) {
					extendedJTATransaction = jndiService().locate( "java:comp/websphere/ExtendedJTATransaction" );
				}
			}

			@Override
			public void registerSynchronization(final Synchronization synchronization)
					throws RollbackException, IllegalStateException,
					SystemException {

				final InvocationHandler ih = new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						if ( "afterCompletion".equals( method.getName() ) ) {
							int status = args[2].equals(Boolean.TRUE) ?
									Status.STATUS_COMMITTED :
									Status.STATUS_UNKNOWN;
							synchronization.afterCompletion(status);
						}
						else if ( "beforeCompletion".equals( method.getName() ) ) {
							synchronization.beforeCompletion();
						}
						else if ( "toString".equals( method.getName() ) ) {
							return synchronization.toString();
						}
						return null;
					}

				};

				final Object synchronizationCallback = Proxy.newProxyInstance(
						getClass().getClassLoader(),
						new Class[] {synchronizationCallbackClass},
						ih
				);

				try {
					registerSynchronizationMethod.invoke( extendedJTATransaction, synchronizationCallback );
				}
				catch (Exception e) {
					throw new HibernateException(e);
				}

			}

			@Override
			public int hashCode() {
				return getLocalId().hashCode();
			}

			@Override
			public boolean equals(Object other) {
				if ( !(other instanceof TransactionAdapter) ) {
					return false;
				}
				TransactionAdapter that = (TransactionAdapter) other;
				return getLocalId().equals( that.getLocalId() );
			}

			private Object getLocalId() throws HibernateException {
				try {
					return getLocalIdMethod.invoke( extendedJTATransaction, (Object[]) null );
				}
				catch ( Exception e ) {
					throw new HibernateException( e );
				}
			}

			@Override
			public void commit() throws UnsupportedOperationException {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean delistResource(XAResource resource, int i) throws UnsupportedOperationException {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean enlistResource(XAResource resource) throws UnsupportedOperationException {
				throw new UnsupportedOperationException();
			}

			@Override
			public int getStatus() {
				return Integer.valueOf( 0 ).equals( getLocalId() ) ?
						Status.STATUS_NO_TRANSACTION : Status.STATUS_ACTIVE;
			}

			@Override
			public void rollback() throws UnsupportedOperationException {
				throw new UnsupportedOperationException();
			}

			@Override
			public void setRollbackOnly() throws UnsupportedOperationException {
				throw new UnsupportedOperationException();
			}
		}
	}

}
