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 javax.transaction.Status;
019    import javax.transaction.Synchronization;
020    
021    import org.apache.ojb.broker.PersistenceBroker;
022    import org.apache.ojb.broker.accesslayer.ConnectionManagerIF;
023    import org.apache.ojb.broker.util.logging.Logger;
024    import org.apache.ojb.broker.util.logging.LoggerFactory;
025    import org.odmg.LockNotGrantedException;
026    import org.odmg.ODMGRuntimeException;
027    import org.odmg.TransactionAbortedException;
028    
029    /**
030     * Implementation for use in managed environments.
031     *
032     * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
033     * @version $Id: J2EETransactionImpl.java,v 1.1 2007-08-24 22:17:37 ewestfal Exp $
034     */
035    public class J2EETransactionImpl extends TransactionImpl implements Synchronization
036    {
037        private Logger log = LoggerFactory.getLogger(J2EETransactionImpl.class);
038        private boolean isInExternTransaction;
039    
040        /**
041         * beforeCompletion is being called twice in JBoss, so this
042         * isPrepared flag prevents code from executing twice.
043         * todo: find out why it's being called twice and fix it.
044         */
045        private boolean beforeCompletionCall = false;
046        private boolean afterCompletionCall = false;
047    
048        public J2EETransactionImpl(ImplementationImpl implementation)
049        {
050            super(implementation);
051            isInExternTransaction = false;
052        }
053    
054        public void setInExternTransaction(boolean mode)
055        {
056            isInExternTransaction = mode;
057            }
058    
059        public boolean isInExternTransaction()
060        {
061            return isInExternTransaction;
062        }
063    
064        public void join()
065        {
066                throw new UnsupportedOperationException("Not supported in managed enviroment");
067            }
068    
069        public void leave()
070        {
071                throw new UnsupportedOperationException("Not supported in managed enviroment");
072            }
073    
074        public void checkpoint()
075        {
076                throw new UnsupportedOperationException("Not supported in managed enviroment");
077            }
078    
079        /**
080         * FOR internal use. This method was called after the external transaction was completed.
081         *
082         * @see javax.transaction.Synchronization
083         */
084        public void afterCompletion(int status)
085        {
086            if(afterCompletionCall) return;
087    
088            log.info("Method afterCompletion was called");
089            try
090            {
091                switch(status)
092                {
093                    case Status.STATUS_COMMITTED:
094                        if(log.isDebugEnabled())
095                        {
096                            log.debug("Method afterCompletion: Do commit internal odmg-tx, status of JTA-tx is " + TxUtil.getStatusString(status));
097                        }
098                        commit();
099                        break;
100                    default:
101                        log.error("Method afterCompletion: Do abort call on internal odmg-tx, status of JTA-tx is " + TxUtil.getStatusString(status));
102                        abort();
103                }
104            }
105            finally
106            {
107                afterCompletionCall = true;
108                log.info("Method afterCompletion finished");
109            }
110        }
111    
112        /**
113         * FOR internal use. This method was called before the external transaction was completed.
114         *
115         * This method was called by the JTA-TxManager before the JTA-tx prepare call. Within this method
116         * we prepare odmg for commit and pass all modified persistent objects to DB and release/close the used
117         * connection. We have to close the connection in this method, because the TxManager does prepare for commit
118         * after this method and all used DataSource-connections have to be closed before.
119         *
120         * @see javax.transaction.Synchronization
121         */
122        public void beforeCompletion()
123        {
124            // avoid redundant calls
125            if(beforeCompletionCall) return;
126    
127            log.info("Method beforeCompletion was called");
128            int status = Status.STATUS_UNKNOWN;
129            try
130            {
131                JTATxManager mgr = (JTATxManager) getImplementation().getTxManager();
132                status = mgr.getJTATransaction().getStatus();
133                // ensure proper work, check all possible status
134                // normally only check for 'STATUS_MARKED_ROLLBACK' is necessary
135                if(status == Status.STATUS_MARKED_ROLLBACK
136                        || status == Status.STATUS_ROLLEDBACK
137                        || status == Status.STATUS_ROLLING_BACK
138                        || status == Status.STATUS_UNKNOWN
139                        || status == Status.STATUS_NO_TRANSACTION)
140                {
141                    log.error("Synchronization#beforeCompletion: Can't prepare for commit, because tx status was "
142                            + TxUtil.getStatusString(status) + ". Do internal cleanup only.");
143                }
144                else
145                {
146                    if(log.isDebugEnabled())
147                    {
148                        log.debug("Synchronization#beforeCompletion: Prepare for commit");
149                    }
150                    // write objects to database
151                    prepareCommit();
152                }
153            }
154            catch(Exception e)
155            {
156                log.error("Synchronization#beforeCompletion: Error while prepare for commit", e);
157                if(e instanceof LockNotGrantedException)
158                {
159                    throw (LockNotGrantedException) e;
160                }
161                else if(e instanceof TransactionAbortedException)
162                {
163                    throw (TransactionAbortedException) e;
164                }
165                else if(e instanceof ODMGRuntimeException)
166                {
167                    throw (ODMGRuntimeException) e;
168                }
169                else
170                { 
171                    throw new ODMGRuntimeException("Method beforeCompletion() fails, status of JTA-tx was "
172                            + TxUtil.getStatusString(status) + ", message: " + e.getMessage());
173                }
174    
175            }
176            finally
177            {
178                beforeCompletionCall = true;
179                setInExternTransaction(false);
180                internalCleanup();
181            }
182        }
183    
184        /**
185         * In managed environment do internal close the used connection
186         */
187        private void internalCleanup()
188        {
189            if(hasBroker())
190            {
191                PersistenceBroker broker = getBroker();
192                if(log.isDebugEnabled())
193                {
194                    log.debug("Do internal cleanup and close the internal used connection without" +
195                            " closing the used broker");
196                }
197                ConnectionManagerIF cm = broker.serviceConnectionManager();
198                if(cm.isInLocalTransaction())
199                {
200                    /*
201                    arminw:
202                    in managed environment this call will be ignored because, the JTA transaction
203                    manager control the connection status. But to make connectionManager happy we
204                    have to complete the "local tx" of the connectionManager before release the
205                    connection
206                    */
207                    cm.localCommit();
208                }
209                cm.releaseConnection();
210            }
211        }
212    
213        public void commit()
214        {
215            try
216            {
217                // prepare for commit was done before on 'beforeCompleation' call
218                if(log.isDebugEnabled()) log.debug("Commit transaction " + this + ", commit on broker " + broker);
219                if(hasBroker())
220                {
221                    getBroker().commitTransaction();
222                    doClose();
223                }
224                setStatus(Status.STATUS_COMMITTED);
225                // Now, we notify everything the commit is done.
226                performTransactionAwareAfterCommit();
227            }
228            catch(Exception ex)
229            {
230                // We should not reach this block
231                log.error("Unexpected error while do commit on used PB-Instance and close resources", ex);
232                abort();
233            }
234        }
235    
236        public void abort()
237        {
238            if(getStatus() == Status.STATUS_ROLLEDBACK) return;
239    
240            try
241            {
242                try
243                {
244                    doAbort();
245                }
246                catch(Exception ignore)
247                {
248                    log.error("Failure while do abort call", ignore);
249                }
250    
251                getImplementation().getTxManager().abortExternalTx(this);
252    
253                try
254                {
255                    doClose();
256                }
257                catch(Exception e)
258                {
259                    log.error("Failure while do abort call", e);
260                }
261                setStatus(Status.STATUS_ROLLEDBACK);
262            }
263            finally
264            {
265                setInExternTransaction(false);
266            }
267        }
268    }