001    package org.apache.ojb.odmg;
002    
003    /* Copyright 2002-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import org.apache.ojb.broker.OJBRuntimeException;
019    import org.apache.ojb.broker.util.configuration.Configuration;
020    import org.apache.ojb.broker.util.logging.Logger;
021    import org.apache.ojb.broker.util.logging.LoggerFactory;
022    import org.apache.ojb.broker.transaction.tm.TransactionManagerFactoryException;
023    import org.apache.ojb.broker.transaction.tm.TransactionManagerFactoryFactory;
024    import org.apache.commons.lang.SystemUtils;
025    import org.odmg.TransactionNotInProgressException;
026    
027    import javax.transaction.Status;
028    import javax.transaction.SystemException;
029    import javax.transaction.Transaction;
030    import javax.transaction.TransactionManager;
031    import java.lang.ref.WeakReference;
032    
033    /**
034     * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
035     *
036     * In an app server environment, where we have a transaction manager, we
037     * use the transactionmanager and it's associated JTA transaction to associate
038     * with the ODMG transaction. So the key is retrieved by calling getTransaction
039     * on the transactionManager
040     */
041    public class JTATxManager implements OJBTxManager
042    {
043        private Logger log = LoggerFactory.getLogger(JTATxManager.class);
044        private static ThreadLocal txRepository = new ThreadLocal();
045    
046        /**
047         * Remove the ODMG transaction from the transaction buffer
048         * ODMG transactions are associated with JTA transactions via a map
049         */
050        public void deregisterTx(Object transaction)
051        {
052    //        TxBuffer buf = (TxBuffer) txRepository.get();
053    //        if (buf != null)
054    //        {
055    //            buf.setInternTx(null);
056    //        }
057            txRepository.set(null);
058        }
059    
060        public void registerTx(TransactionImpl odmgTrans)
061        {
062            if (log.isDebugEnabled()) log.debug("registerSynchronization was called");
063            Transaction transaction = null;
064            try
065            {
066                transaction = getJTATransaction();
067            }
068            catch (SystemException e)
069            {
070                log.error("Obtain current transaction from container failed", e);
071            }
072            if (transaction == null)
073            {
074                log.error("Cannot get the external transaction from the external TM");
075                throw new TransactionNotInProgressException("No external transaction found");
076            }
077            if (log.isDebugEnabled())
078            {
079                log.debug("registerSynchronization was called with parameters"
080                        + SystemUtils.LINE_SEPARATOR +"J2EETransactionImpl: " + odmgTrans
081                        + SystemUtils.LINE_SEPARATOR + "Transaction: " + transaction);
082            }
083            registerSynchronization(odmgTrans, transaction);
084        }
085    
086        /**
087         * Do synchronization of the given J2EE ODMG Transaction
088         */
089        private void registerSynchronization(TransactionImpl odmgTrans, Transaction transaction)
090        {
091            // todo only need for development
092            if (odmgTrans == null || transaction == null)
093            {
094                log.error("One of the given parameters was null --> cannot do synchronization!" +
095                        " omdg transaction was null: " + (odmgTrans == null) +
096                        ", external transaction was null: " + (transaction == null));
097                return;
098            }
099    
100            int status = -1; // default status.
101            try
102            {
103                status = transaction.getStatus();
104                if (status != Status.STATUS_ACTIVE)
105                {
106                    throw new OJBRuntimeException(
107                            "Transaction synchronization failed - wrong status of external container tx: " +
108                            getStatusString(status));
109                }
110            }
111            catch (SystemException e)
112            {
113                throw new OJBRuntimeException("Can't read status of external tx", e);
114            }
115    
116            try
117            {
118                //Sequence of the following method calls is significant
119                // 1. register the synchronization with the ODMG notion of a transaction.
120                transaction.registerSynchronization((J2EETransactionImpl) odmgTrans);
121                // 2. mark the ODMG transaction as being in a JTA Transaction
122                // Associate external transaction with the odmg transaction.
123                txRepository.set(new TxBuffer(odmgTrans, transaction));
124            }
125            catch (Exception e)
126            {
127                log.error("Cannot associate PersistenceBroker with running Transaction", e);
128                throw new OJBRuntimeException(
129                        "Transaction synchronization failed - wrong status of external container tx", e);
130            }
131        }
132    
133        private static String getStatusString(int status)
134        {
135            switch (status)
136            {
137                case Status.STATUS_ACTIVE:
138                    return "STATUS_ACTIVE";
139                case Status.STATUS_COMMITTED:
140                    return "STATUS_COMMITTED";
141                case Status.STATUS_COMMITTING:
142                    return "STATUS_COMMITTING";
143                case Status.STATUS_MARKED_ROLLBACK:
144                    return "STATUS_MARKED_ROLLBACK";
145                case Status.STATUS_NO_TRANSACTION:
146                    return "STATUS_NO_TRANSACTION";
147                case Status.STATUS_PREPARED:
148                    return "STATUS_PREPARED";
149                case Status.STATUS_PREPARING:
150                    return "STATUS_PREPARING";
151                case Status.STATUS_ROLLEDBACK:
152                    return "STATUS_ROLLEDBACK";
153                case Status.STATUS_ROLLING_BACK:
154                    return "STATUS_ROLLING_BACK";
155                case Status.STATUS_UNKNOWN:
156                    return "STATUS_UNKNOWN";
157                default:
158                    return "NO STATUS FOUND";
159            }
160        }
161    
162        /**
163         * Return the TransactionManager of the external app
164         */
165        private TransactionManager getTransactionManager()
166        {
167            TransactionManager retval = null;
168            try
169            {
170                if (log.isDebugEnabled()) log.debug("getTransactionManager called");
171                retval = TransactionManagerFactoryFactory.instance().getTransactionManager();
172            }
173            catch (TransactionManagerFactoryException e)
174            {
175                log.warn("Exception trying to obtain TransactionManager from Factory", e);
176                e.printStackTrace();
177            }
178            return retval;
179        }
180    
181        public Transaction getJTATransaction() throws SystemException
182        {
183            if (log.isDebugEnabled()) log.debug("getTransaction called");
184            if (getTransactionManager() == null)
185            {
186                log.warn("TransactionManager was null");
187                return null;
188            }
189            return getTransactionManager().getTransaction();
190        }
191    
192        /**
193         * Returns the current transaction based on the JTA Transaction.
194         * @throws org.odmg.TransactionNotInProgressException if no transaction was found.
195         */
196        public TransactionImpl getCurrentTransaction()
197        {
198            TransactionImpl retval = getTransaction();
199            if (null == retval)
200            {
201                throw new TransactionNotInProgressException(
202                        "Calling method needed transaction, but no transaction found via TransactionManager");
203            }
204            return retval;
205        }
206    
207        /**
208         * Returns the current transaction based on the JTA Transaction or <code>null</code>
209         * if no transaction was found.
210         */
211        public TransactionImpl getTransaction()
212        {
213            TxBuffer buf = (TxBuffer) txRepository.get();
214            return buf != null ? buf.getInternTx() : null;
215        }
216    
217        /**
218         * Abort an active extern transaction associated with the given PB.
219         */
220        public void abortExternalTx(TransactionImpl odmgTrans)
221        {
222            if (log.isDebugEnabled()) log.debug("abortExternTransaction was called");
223            if (odmgTrans == null) return;
224            TxBuffer buf = (TxBuffer) txRepository.get();
225            Transaction extTx = buf != null ? buf.getExternTx() : null;
226            try
227            {
228                if (extTx != null && extTx.getStatus() == Status.STATUS_ACTIVE)
229                {
230                    if(log.isDebugEnabled())
231                    {
232                        log.debug("Set extern transaction to rollback");
233                    }
234                    extTx.setRollbackOnly();
235                }
236            }
237            catch (Exception ignore)
238            {
239            }
240            txRepository.set(null);
241        }
242    
243        public void configure(Configuration config)
244        {
245            /**
246             * no-op
247             */
248        }
249    
250    
251        //************************************************************************
252        // inner class
253        //************************************************************************
254        private static final class TxBuffer
255        {
256            private WeakReference externTx = null;
257            private WeakReference internTx = null;
258    
259            public TxBuffer()
260            {
261            }
262    
263            /*
264            arminw:
265            use WeakReference to make sure that closed Transaction objects can be
266            immediately reclaimed by the garbage collector.
267            */
268    
269            public TxBuffer(TransactionImpl internTx, Transaction externTx)
270            {
271                this.internTx = new WeakReference(internTx);
272                this.externTx = new WeakReference(externTx);
273            }
274    
275            public Transaction getExternTx()
276            {
277                return (Transaction) externTx.get();
278            }
279    
280            public void setExternTx(Transaction externTx)
281            {
282                this.externTx = new WeakReference(externTx);
283            }
284    
285            public TransactionImpl getInternTx()
286            {
287                return (TransactionImpl) internTx.get();
288            }
289    
290            public void setInternTx(TransactionImpl internTx)
291            {
292                this.internTx = new WeakReference(internTx);
293            }
294        }
295    }