/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.dqp.internal.process; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import javax.resource.NotSupportedException; import javax.resource.spi.XATerminator; import javax.resource.spi.work.WorkException; import javax.resource.spi.work.WorkManager; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.InvalidTransactionException; import javax.transaction.RollbackException; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import org.teiid.adminapi.AdminException; import org.teiid.adminapi.AdminProcessingException; import org.teiid.adminapi.impl.TransactionMetadata; import org.teiid.client.xa.XATransactionException; import org.teiid.client.xa.XidImpl; import org.teiid.core.util.Assertion; import org.teiid.dqp.service.TransactionContext; import org.teiid.dqp.service.TransactionContext.Scope; import org.teiid.dqp.service.TransactionService; import org.teiid.query.QueryPlugin; /** * Note that the begin methods do not leave the transaction associated with the * calling thread. This is by design and requires explicit resumes for association. */ public class TransactionServerImpl implements TransactionService { protected static class TransactionMapping { // (connection -> transaction for global and local) private Map<String, TransactionContext> threadToTransactionContext = new HashMap<String, TransactionContext>(); // (MMXid -> global transactions keyed) private Map<Xid, TransactionContext> xidToTransactionContext = new HashMap<Xid, TransactionContext>(); public synchronized TransactionContext getOrCreateTransactionContext(String threadId) { TransactionContext tc = threadToTransactionContext.get(threadId); if (tc == null) { tc = new TransactionContext(); tc.setThreadId(threadId); threadToTransactionContext.put(threadId, tc); } return tc; } public synchronized TransactionContext getTransactionContext(String threadId) { return threadToTransactionContext.get(threadId); } public synchronized TransactionContext getTransactionContext(XidImpl xid) { return xidToTransactionContext.get(xid); } public synchronized TransactionContext removeTransactionContext(String threadId) { return threadToTransactionContext.remove(threadId); } public synchronized void removeTransactionContext(TransactionContext tc) { if (tc.getXid() != null) { this.xidToTransactionContext.remove(tc.getXid()); } if (tc.getThreadId() != null) { this.threadToTransactionContext.remove(tc.getThreadId()); } } public synchronized void addTransactionContext(TransactionContext tc) { if (tc.getXid() != null) { this.xidToTransactionContext.put(tc.getXid(), tc); } if (tc.getThreadId() != null) { this.threadToTransactionContext.put(tc.getThreadId(), tc); } } } protected TransactionMapping transactions = new TransactionMapping(); private XATerminator xaTerminator; protected TransactionManager transactionManager; private WorkManager workManager; private boolean detectTransactions; public void setDetectTransactions(boolean detectTransactions) { this.detectTransactions = detectTransactions; } public boolean isDetectTransactions() { return detectTransactions; } public void setXaTerminator(XATerminator xaTerminator) { this.xaTerminator = xaTerminator; } public void setTransactionManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } public void setWorkManager(WorkManager workManager) { this.workManager = workManager; } /** * Global Transaction */ public int prepare(final String threadId, XidImpl xid, boolean singleTM) throws XATransactionException { TransactionContext tc = checkXAState(threadId, xid, true, false); if (!tc.getSuspendedBy().isEmpty()) { throw new XATransactionException(QueryPlugin.Event.TEIID30505, XAException.XAER_PROTO, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30505, xid)); } // In the container this pass though if (singleTM) { return XAResource.XA_RDONLY; } try { return this.xaTerminator.prepare(tc.getXid()); } catch (XAException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30506, e); } } /** * Global Transaction */ public void commit(final String threadId, XidImpl xid, boolean onePhase, boolean singleTM) throws XATransactionException { TransactionContext tc = checkXAState(threadId, xid, true, false); try { if (singleTM || (onePhase && XAResource.XA_RDONLY == prepare(threadId, xid, singleTM))) { return; //nothing to do } //TODO: we have no way of knowing for sure if we can safely use the onephase optimization this.xaTerminator.commit(tc.getXid(), false); } catch (XAException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30507, e); } finally { this.transactions.removeTransactionContext(tc); } } /** * Global Transaction */ public void rollback(final String threadId, XidImpl xid, boolean singleTM) throws XATransactionException { TransactionContext tc = checkXAState(threadId, xid, true, false); try { // In the case of single TM, the container directly roll backs the sources. if (!singleTM) { this.xaTerminator.rollback(tc.getXid()); } } catch (XAException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30508, e); } finally { this.transactions.removeTransactionContext(tc); } } /** * Global Transaction */ public Xid[] recover(int flag, boolean singleTM) throws XATransactionException { // In case of single TM, container knows this list. if (singleTM) { return new Xid[0]; } try { return this.xaTerminator.recover(flag); } catch (XAException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30509, e); } } /** * Global Transaction */ public void forget(final String threadId, XidImpl xid, boolean singleTM) throws XATransactionException { TransactionContext tc = checkXAState(threadId, xid, true, false); try { if (singleTM) { return; } this.xaTerminator.forget(xid); } catch (XAException err) { throw new XATransactionException(QueryPlugin.Event.TEIID30510, err); } finally { this.transactions.removeTransactionContext(tc); } } /** * Global Transaction */ public void start(final String threadId, final XidImpl xid, int flags, int timeout, boolean singleTM) throws XATransactionException { TransactionContext tc = null; switch (flags) { case XAResource.TMNOFLAGS: { try { checkXAState(threadId, xid, false, false); tc = transactions.getOrCreateTransactionContext(threadId); if (tc.getTransactionType() != TransactionContext.Scope.NONE) { throw new XATransactionException(QueryPlugin.Event.TEIID30517, XAException.XAER_PROTO, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30517)); } tc.setTransactionTimeout(timeout); tc.setXid(xid); tc.setTransactionType(TransactionContext.Scope.GLOBAL); if (singleTM) { tc.setTransaction(transactionManager.getTransaction()); if (tc.getTransaction() == null) { //the current code currently does not handle the case of embedded connections where //someone is manually initiating txns - that is there is no thread bound txn. //in theory we could inflow the txn and then change all of the methods to check singleTM off of the context throw new XATransactionException(QueryPlugin.Event.TEIID30590, XAException.XAER_INVAL, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30590)); } } else { FutureWork<Transaction> work = new FutureWork<Transaction>(new Callable<Transaction>() { @Override public Transaction call() throws Exception { return transactionManager.getTransaction(); } }, 0); workManager.doWork(work, WorkManager.INDEFINITE, tc, null); tc.setTransaction(work.get()); } } catch (NotSupportedException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30512, XAException.XAER_INVAL, e); } catch (WorkException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30512, XAException.XAER_INVAL, e); } catch (InterruptedException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30512, XAException.XAER_INVAL, e); } catch (ExecutionException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30512, XAException.XAER_INVAL, e); } catch (SystemException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30512, XAException.XAER_INVAL, e); } break; } case XAResource.TMJOIN: case XAResource.TMRESUME: { tc = checkXAState(threadId, xid, true, false); TransactionContext threadContext = transactions.getOrCreateTransactionContext(threadId); if (threadContext.getTransactionType() != TransactionContext.Scope.NONE) { throw new XATransactionException(QueryPlugin.Event.TEIID30517, XAException.XAER_PROTO, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30517)); } if (flags == XAResource.TMRESUME && !tc.getSuspendedBy().remove(threadId)) { throw new XATransactionException(QueryPlugin.Event.TEIID30518, XAException.XAER_PROTO, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30518, new Object[] {xid, threadId})); } break; } default: throw new XATransactionException(QueryPlugin.Event.TEIID30519, XAException.XAER_INVAL, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30519)); } tc.setThreadId(threadId); transactions.addTransactionContext(tc); } /** * Global Transaction */ public void end(final String threadId, XidImpl xid, int flags, boolean singleTM) throws XATransactionException { TransactionContext tc = checkXAState(threadId, xid, true, true); try { switch (flags) { case XAResource.TMSUSPEND: { tc.getSuspendedBy().add(threadId); break; } case XAResource.TMSUCCESS: { //TODO: should close all statements break; } case XAResource.TMFAIL: { cancelTransactions(threadId, false); break; } default: throw new XATransactionException(QueryPlugin.Event.TEIID30520, XAException.XAER_INVAL, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30520)); } } finally { tc.setThreadId(null); transactions.removeTransactionContext(threadId); } } private TransactionContext checkXAState(final String threadId, final XidImpl xid, boolean transactionExpected, boolean threadBound) throws XATransactionException { TransactionContext tc = transactions.getTransactionContext(xid); if (transactionExpected && tc == null) { throw new XATransactionException(QueryPlugin.Event.TEIID30521, XAException.XAER_NOTA, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30521, xid)); } else if (!transactionExpected) { if (tc != null) { throw new XATransactionException(QueryPlugin.Event.TEIID30522, XAException.XAER_DUPID, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30522, new Object[] {xid})); } if (!threadBound) { tc = transactions.getOrCreateTransactionContext(threadId); if (tc.getTransactionType() != TransactionContext.Scope.NONE) { throw new XATransactionException(QueryPlugin.Event.TEIID30517, XAException.XAER_PROTO, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30517)); } } return null; } if (threadBound) { if (!threadId.equals(tc.getThreadId())) { throw new XATransactionException(QueryPlugin.Event.TEIID30524, XAException.XAER_PROTO, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30524, xid)); } } else if (tc.getThreadId() != null) { throw new XATransactionException(QueryPlugin.Event.TEIID30525, XAException.XAER_PROTO, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30525, xid)); } return tc; } private TransactionContext checkLocalTransactionState(String threadId, boolean transactionExpected) throws XATransactionException { final TransactionContext tc = transactions.getOrCreateTransactionContext(threadId); //TODO: this check is only really needed in local mode if (!transactionExpected && detectTransactions) { try { Transaction tx = transactionManager.getTransaction(); if (tx != null && tx != tc.getTransaction()) { throw new XATransactionException(QueryPlugin.Event.TEIID30517, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30517)); } } catch (SystemException e) { } catch (IllegalStateException e) { } } try { if (tc.getTransactionType() != TransactionContext.Scope.NONE) { if (tc.getTransactionType() != TransactionContext.Scope.LOCAL || !transactionExpected) { throw new XATransactionException(QueryPlugin.Event.TEIID30517, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30517)); } transactionManager.resume(tc.getTransaction()); } else if (transactionExpected) { throw new InvalidTransactionException(QueryPlugin.Util.getString("TransactionServer.no_transaction", threadId)); //$NON-NLS-1$ } } catch (InvalidTransactionException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30526, e); } catch (SystemException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30527, e); } return tc; } private void beginDirect(TransactionContext tc) throws XATransactionException { try { transactionManager.begin(); Transaction tx = transactionManager.suspend(); tc.setTransaction(tx); tc.setCreationTime(System.currentTimeMillis()); } catch (javax.transaction.NotSupportedException err) { throw new XATransactionException(QueryPlugin.Event.TEIID30528, err); } catch (SystemException err) { throw new XATransactionException(QueryPlugin.Event.TEIID30528, err); } } private void commitDirect(TransactionContext context) throws XATransactionException { try { transactionManager.commit(); } catch (SecurityException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30530, e); } catch (RollbackException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30530, e); } catch (HeuristicMixedException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30530, e); } catch (HeuristicRollbackException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30530, e); } catch (SystemException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30530, e); } finally { transactions.removeTransactionContext(context); } } private void rollbackDirect(TransactionContext tc) throws XATransactionException { try { this.transactionManager.rollback(); } catch (SecurityException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30535, e); } catch (SystemException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30535, e); } catch (IllegalStateException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30535, e); } finally { transactions.removeTransactionContext(tc); } } public void suspend(TransactionContext context) throws XATransactionException { try { this.transactionManager.suspend(); } catch (SystemException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30537, e); } } public void resume(TransactionContext context) throws XATransactionException { try { //if we're already associated, just return if (this.transactionManager.getTransaction() == context.getTransaction()) { return; } } catch (SystemException e) { } try { this.transactionManager.resume(context.getTransaction()); } catch (IllegalStateException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30538, e); } catch (InvalidTransactionException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30538, e); } catch (SystemException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30538, e); } } /** * Local Transaction */ public TransactionContext begin(String threadId) throws XATransactionException { TransactionContext tc = checkLocalTransactionState(threadId, false); beginDirect(tc); tc.setTransactionType(TransactionContext.Scope.LOCAL); return tc; } /** * Local Transaction */ public void commit(String threadId) throws XATransactionException { TransactionContext tc = checkLocalTransactionState(threadId, true); commitDirect(tc); } /** * Local Transaction */ public void rollback(String threadId) throws XATransactionException { TransactionContext tc = checkLocalTransactionState(threadId, true); rollbackDirect(tc); } public TransactionContext getOrCreateTransactionContext(final String threadId) { TransactionContext tc = transactions.getOrCreateTransactionContext(threadId); if (detectTransactions) { try { Transaction tx = transactionManager.getTransaction(); if (tx != null && tx != tc.getTransaction()) { tx.registerSynchronization(new Synchronization() { @Override public void beforeCompletion() { } @Override public void afterCompletion(int status) { transactions.removeTransactionContext(threadId); } }); tc.setTransaction(tx); tc.setTransactionType(Scope.GLOBAL); } //TODO: it may be appropriate to throw an up-front exception } catch (SystemException e) { } catch (IllegalStateException e) { } catch (RollbackException e) { } } return tc; } /** * Request level transaction */ public void begin(TransactionContext context) throws XATransactionException{ if (context.getTransactionType() != TransactionContext.Scope.NONE) { throw new XATransactionException(QueryPlugin.Event.TEIID30517, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30517)); } beginDirect(context); context.setTransactionType(TransactionContext.Scope.REQUEST); this.transactions.addTransactionContext(context); //it may have been removed if this is a block level operation } /** * Request level transaction */ public void commit(TransactionContext context) throws XATransactionException { Assertion.assertTrue(context.getTransactionType() == TransactionContext.Scope.REQUEST); try { commitDirect(context); } finally { context.setTransaction(null); context.setTransactionType(Scope.NONE); } } /** * Request level transaction */ public void rollback(TransactionContext context) throws XATransactionException { Assertion.assertTrue(context.getTransactionType() == TransactionContext.Scope.REQUEST); try { rollbackDirect(context); } finally { context.setTransaction(null); context.setTransactionType(Scope.NONE); } } public void cancelTransactions(String threadId, boolean requestOnly) throws XATransactionException { TransactionContext tc = requestOnly?transactions.getTransactionContext(threadId):transactions.removeTransactionContext(threadId); if (tc == null || tc.getTransactionType() == TransactionContext.Scope.NONE || (requestOnly && tc.getTransactionType() != TransactionContext.Scope.REQUEST)) { return; } try { tc.getTransaction().setRollbackOnly(); } catch (SystemException e) { throw new XATransactionException(QueryPlugin.Event.TEIID30541, e); } } @Override public Collection<TransactionMetadata> getTransactions() { Set<TransactionContext> txnSet = Collections.newSetFromMap(new IdentityHashMap<TransactionContext, Boolean>()); synchronized (this.transactions) { txnSet.addAll(this.transactions.threadToTransactionContext.values()); txnSet.addAll(this.transactions.xidToTransactionContext.values()); } Collection<TransactionMetadata> result = new ArrayList<TransactionMetadata>(txnSet.size()); for (TransactionContext transactionContext : txnSet) { if (transactionContext.getTransactionType() == Scope.NONE) { continue; } TransactionMetadata txnImpl = new TransactionMetadata(); txnImpl.setAssociatedSession(transactionContext.getThreadId()); txnImpl.setCreatedTime(transactionContext.getCreationTime()); txnImpl.setScope(transactionContext.getTransactionType().toString()); txnImpl.setId(transactionContext.getTransactionId()); result.add(txnImpl); } return result; } @Override public void terminateTransaction(String threadId) throws AdminException { if (threadId == null) { return; } try { cancelTransactions(threadId, false); } catch (XATransactionException e) { throw new AdminProcessingException(QueryPlugin.Event.TEIID30542, e); } } }