001    package org.apache.ojb.broker.accesslayer;
002    
003    /* Copyright 2003-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 java.sql.Connection;
019    import java.sql.SQLException;
020    
021    import org.apache.ojb.broker.OJBRuntimeException;
022    import org.apache.ojb.broker.PBKey;
023    import org.apache.ojb.broker.PersistenceBroker;
024    import org.apache.ojb.broker.PersistenceBrokerException;
025    import org.apache.ojb.broker.TransactionAbortedException;
026    import org.apache.ojb.broker.TransactionInProgressException;
027    import org.apache.ojb.broker.TransactionNotInProgressException;
028    import org.apache.ojb.broker.core.PersistenceBrokerImpl;
029    import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
030    import org.apache.ojb.broker.metadata.MetadataManager;
031    import org.apache.ojb.broker.platforms.Platform;
032    import org.apache.ojb.broker.platforms.PlatformFactory;
033    import org.apache.ojb.broker.util.batch.BatchConnection;
034    import org.apache.ojb.broker.util.logging.Logger;
035    import org.apache.ojb.broker.util.logging.LoggerFactory;
036    
037    /**
038     * Manages Connection ressources.
039     *
040     * @see ConnectionManagerIF
041     * @author Thomas Mahler
042     * @version $Id: ConnectionManagerImpl.java,v 1.1 2007-08-24 22:17:30 ewestfal Exp $
043     */
044    public class ConnectionManagerImpl implements ConnectionManagerIF
045    {
046        private Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class);
047    
048        private PersistenceBrokerImpl broker = null;
049        private ConnectionFactory connectionFactory;
050        private JdbcConnectionDescriptor jcd;
051        private Platform platform;
052        private Connection con = null;
053        private PBKey pbKey;
054        private boolean originalAutoCommitState;
055        private boolean isInLocalTransaction;
056        private boolean batchMode;
057        private BatchConnection batchCon = null;
058    
059        public ConnectionManagerImpl(PersistenceBroker broker)
060        {
061            // TODO: avoid this cast
062            this.broker = (PersistenceBrokerImpl) broker;
063            this.pbKey = broker.getPBKey();
064            this.jcd = MetadataManager.getInstance().connectionRepository().getDescriptor(pbKey);
065            this.connectionFactory = ConnectionFactoryFactory.getInstance().createConnectionFactory();
066            this.platform = PlatformFactory.getPlatformFor(jcd);
067            /*
068            by default batch mode is not enabled and after use of a PB
069            instance, before instance was returned to pool, batch mode
070            was set to false again (PB implementation close method)
071            Be carefully in modify this behaviour, changes could cause
072            unexpected behaviour
073            */
074            setBatchMode(false);
075        }
076    
077        /**
078         * Returns the associated {@link org.apache.ojb.broker.metadata.JdbcConnectionDescriptor}
079         */
080        public JdbcConnectionDescriptor getConnectionDescriptor()
081        {
082            return jcd;
083        }
084    
085        public Platform getSupportedPlatform()
086        {
087            return this.platform;
088        }
089    
090        /**
091         * Returns the underlying connection, requested from
092         * {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}.
093         * <p>
094         * PB#beginTransaction() opens a single jdbc connection via
095             * PB#serviceConnectionManager().localBegin().
096             * If you call PB#serviceConnectionManager().getConnection() later
097             * it returns the already opened connection.
098             * The PB instance will release the used connection during
099             * PB#commitTransaction() or PB#abortTransaction() or PB#close().
100         * </p>
101         * <p>
102         * NOTE: Never call Connection.close() on the connection requested from the ConnectionManager.
103         * Cleanup of used connection is done by OJB itself. If you need to release a used connection
104         * call {@link #releaseConnection()}.
105         * </p>
106         */
107        public Connection getConnection() throws LookupException
108        {
109            /*
110            if the connection is not null and we are not in a local tx, we check
111            the connection state and release "dead" connections.
112            if connection is in local tx we do nothing, the dead connection will cause
113            an exception and PB instance have to handle rollback
114            */
115            if(con != null && !isInLocalTransaction() && !isAlive(con))
116            {
117                releaseConnection();
118            }
119            if (con == null)
120            {
121                con = this.connectionFactory.lookupConnection(jcd);
122                if (con == null) throw new PersistenceBrokerException("Cannot get connection for " + jcd);
123                if (jcd.getUseAutoCommit() == JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE)
124                {
125                    try
126                    {
127                        this.originalAutoCommitState = con.getAutoCommit();
128                    }
129                    catch (SQLException e)
130                    {
131                        throw new PersistenceBrokerException("Cannot request autoCommit state on the connection", e);
132                    }
133                }
134                if (log.isDebugEnabled()) log.debug("Request new connection from ConnectionFactory: " + con);
135            }
136    
137            if (isBatchMode())
138            {
139                if (batchCon == null)
140                {
141                    batchCon = new BatchConnection(con, broker);
142                }
143                return batchCon;
144            }
145            else
146            {
147                return con;
148            }
149        }
150    
151        /**
152         * Start transaction on the underlying connection.
153         */
154        public void localBegin()
155        {
156            if (this.isInLocalTransaction)
157            {
158                throw new TransactionInProgressException("Connection is already in transaction");
159            }
160            Connection connection = null;
161            try
162            {
163                connection = this.getConnection();
164            }
165            catch (LookupException e)
166            {
167                /**
168                 * must throw to notify user that we couldn't start a connection
169                 */
170                throw new PersistenceBrokerException("Can't lookup a connection", e);
171            }
172            if (log.isDebugEnabled()) log.debug("localBegin was called for con " + connection);
173            // change autoCommit state only if we are not in a managed environment
174            // and it is enabled by user
175            if(!broker.isManaged())
176            {
177                if (jcd.getUseAutoCommit() == JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE)
178                {
179                    if (log.isDebugEnabled()) log.debug("Try to change autoCommit state to 'false'");
180                    platform.changeAutoCommitState(jcd, connection, false);
181                }
182            }
183            else
184            {
185                if(log.isDebugEnabled()) log.debug(
186                            "Found managed environment setting in PB, will skip Platform.changeAutoCommitState(...) call");
187            }
188            this.isInLocalTransaction = true;
189        }
190    
191        /**
192         * Call commit on the underlying connection.
193         */
194        public void localCommit()
195        {
196            if (log.isDebugEnabled()) log.debug("commit was called");
197            if (!this.isInLocalTransaction)
198            {
199                throw new TransactionNotInProgressException("Not in transaction, call begin() before commit()");
200            }
201            try
202            {
203                if(!broker.isManaged())
204                {
205                    if (batchCon != null)
206                    {
207                        batchCon.commit();
208                    }
209                    else if (con != null)
210                    {
211                        con.commit();
212                    }
213                }
214                else
215                {
216                    if(log.isDebugEnabled()) log.debug(
217                            "Found managed environment setting in PB, will skip Connection.commit() call");
218                }
219            }
220            catch (SQLException e)
221            {
222                log.error("Commit on underlying connection failed, try to rollback connection", e);
223                this.localRollback();
224                throw new TransactionAbortedException("Commit on connection failed", e);
225            }
226            finally
227            {
228                this.isInLocalTransaction = false;
229                restoreAutoCommitState();
230                this.releaseConnection();
231            }
232        }
233    
234        /**
235         * Call rollback on the underlying connection.
236         */
237        public void localRollback()
238        {
239            log.info("Rollback was called, do rollback on current connection " + con);
240            if (!this.isInLocalTransaction)
241            {
242                throw new PersistenceBrokerException("Not in transaction, cannot abort");
243            }
244            try
245            {
246                //truncate the local transaction
247                this.isInLocalTransaction = false;
248                if(!broker.isManaged())
249                {
250                    if (batchCon != null)
251                    {
252                        batchCon.rollback();
253                    }
254                    else if (con != null && !con.isClosed())
255                    {
256                        con.rollback();
257                    }
258                }
259                else
260                {
261                    if(log.isEnabledFor(Logger.INFO)) log.info(
262                            "Found managed environment setting in PB, will ignore rollback call on connection, this should be done by JTA");
263                }
264            }
265            catch (SQLException e)
266            {
267                log.error("Rollback on the underlying connection failed", e);
268            }
269            finally
270            {
271                try
272                {
273                    restoreAutoCommitState();
274                        }
275                catch(OJBRuntimeException ignore)
276                {
277                                // Ignore or log exception
278                        }
279                releaseConnection();
280            }
281        }
282    
283        /**
284         * Reset autoCommit state.
285         */
286        protected void restoreAutoCommitState()
287        {
288            try
289            {
290                if(!broker.isManaged())
291                {
292                    if (jcd.getUseAutoCommit() == JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE
293                            && originalAutoCommitState == true && con != null && !con.isClosed())
294                    {
295                        platform.changeAutoCommitState(jcd, con, true);
296                    }
297                }
298                else
299                {
300                    if(log.isDebugEnabled()) log.debug(
301                            "Found managed environment setting in PB, will skip Platform.changeAutoCommitState(...) call");
302                }
303            }
304            catch (SQLException e)
305            {
306                // should never be reached
307                throw new OJBRuntimeException("Restore of connection autocommit state failed", e);
308            }
309        }
310    
311        /**
312         * Check if underlying connection was alive.
313         */
314        public boolean isAlive(Connection conn)
315        {
316            try
317            {
318                return con != null ? !con.isClosed() : false;
319            }
320            catch (SQLException e)
321            {
322                log.error("IsAlive check failed, running connection was invalid!!", e);
323                return false;
324            }
325        }
326    
327        public boolean isInLocalTransaction()
328        {
329            return this.isInLocalTransaction;
330        }
331    
332        /**
333         * Release connection to the {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}, make
334         * sure that you call the method in either case, it's the only way to free the connection.
335         */
336        public void releaseConnection()
337        {
338            if (this.con == null)
339            {
340                return;
341            }
342            if(isInLocalTransaction())
343            {
344                log.error("Release connection: connection is in local transaction, missing 'localCommit' or" +
345                        " 'localRollback' call - try to rollback the connection");
346                localRollback();
347            }
348            else
349            {
350                this.connectionFactory.releaseConnection(this.jcd, this.con);
351                this.con = null;
352                this.batchCon = null;
353            }
354        }
355    
356        /**
357         * Returns the underlying used {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}
358         * implementation.
359         */
360        public ConnectionFactory getUnderlyingConnectionFactory()
361        {
362            return connectionFactory;
363        }
364    
365        /**
366         * Sets the batch mode on or off - this
367         * switch only works if you set attribute <code>batch-mode</code>
368         * in <code>jdbc-connection-descriptor</code> true and your database
369         * support batch mode.
370         *
371         * @param mode the batch mode
372         */
373        public void setBatchMode(boolean mode)
374        {
375            /*
376            arminw:
377            if batch mode was set 'false' in repository,
378            never enable it.
379            There are many users having weird problems
380            when batch mode was enabled behind the scenes
381            */
382            batchMode = mode && jcd.getBatchMode();
383        }
384    
385        /**
386         * @return the batch mode.
387         */
388        public boolean isBatchMode()
389        {
390            return batchMode && platform.supportsBatchOperations();
391        }
392    
393        /**
394         * Execute batch (if the batch mode where used).
395         */
396        public void executeBatch() throws OJBBatchUpdateException
397        {
398            if (batchCon != null)
399            {
400                try
401                {
402                    batchCon.executeBatch();
403                }
404                catch (Throwable th)
405                {
406                    throw new OJBBatchUpdateException(th);
407                }
408            }
409        }
410    
411        /**
412         * Execute batch if the number of statements in it
413         * exceeded the limit (if the batch mode where used).
414         */
415        public void executeBatchIfNecessary() throws OJBBatchUpdateException
416        {
417            if (batchCon != null)
418            {
419                try
420                {
421                    batchCon.executeBatchIfNecessary();
422                }
423                catch (Throwable th)
424                {
425                    throw new OJBBatchUpdateException(th);
426                }
427            }
428        }
429    
430        /**
431         * Clear batch (if the batch mode where used).
432         */
433        public void clearBatch()
434        {
435            if (batchCon != null)
436            {
437                batchCon.clearBatch();
438            }
439        }
440    }