View Javadoc

1   package org.apache.ojb.broker.accesslayer;
2   
3   /* Copyright 2003-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import java.sql.Connection;
19  import java.sql.SQLException;
20  
21  import org.apache.ojb.broker.OJBRuntimeException;
22  import org.apache.ojb.broker.PBKey;
23  import org.apache.ojb.broker.PersistenceBroker;
24  import org.apache.ojb.broker.PersistenceBrokerException;
25  import org.apache.ojb.broker.TransactionAbortedException;
26  import org.apache.ojb.broker.TransactionInProgressException;
27  import org.apache.ojb.broker.TransactionNotInProgressException;
28  import org.apache.ojb.broker.core.PersistenceBrokerImpl;
29  import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
30  import org.apache.ojb.broker.metadata.MetadataManager;
31  import org.apache.ojb.broker.platforms.Platform;
32  import org.apache.ojb.broker.platforms.PlatformFactory;
33  import org.apache.ojb.broker.util.batch.BatchConnection;
34  import org.apache.ojb.broker.util.logging.Logger;
35  import org.apache.ojb.broker.util.logging.LoggerFactory;
36  
37  /**
38   * Manages Connection ressources.
39   *
40   * @see ConnectionManagerIF
41   * @author Thomas Mahler
42   * @version $Id: ConnectionManagerImpl.java,v 1.1 2007-08-24 22:17:30 ewestfal Exp $
43   */
44  public class ConnectionManagerImpl implements ConnectionManagerIF
45  {
46      private Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class);
47  
48      private PersistenceBrokerImpl broker = null;
49      private ConnectionFactory connectionFactory;
50      private JdbcConnectionDescriptor jcd;
51      private Platform platform;
52      private Connection con = null;
53      private PBKey pbKey;
54      private boolean originalAutoCommitState;
55      private boolean isInLocalTransaction;
56      private boolean batchMode;
57      private BatchConnection batchCon = null;
58  
59      public ConnectionManagerImpl(PersistenceBroker broker)
60      {
61          // TODO: avoid this cast
62          this.broker = (PersistenceBrokerImpl) broker;
63          this.pbKey = broker.getPBKey();
64          this.jcd = MetadataManager.getInstance().connectionRepository().getDescriptor(pbKey);
65          this.connectionFactory = ConnectionFactoryFactory.getInstance().createConnectionFactory();
66          this.platform = PlatformFactory.getPlatformFor(jcd);
67          /*
68          by default batch mode is not enabled and after use of a PB
69          instance, before instance was returned to pool, batch mode
70          was set to false again (PB implementation close method)
71          Be carefully in modify this behaviour, changes could cause
72          unexpected behaviour
73          */
74          setBatchMode(false);
75      }
76  
77      /**
78       * Returns the associated {@link org.apache.ojb.broker.metadata.JdbcConnectionDescriptor}
79       */
80      public JdbcConnectionDescriptor getConnectionDescriptor()
81      {
82          return jcd;
83      }
84  
85      public Platform getSupportedPlatform()
86      {
87          return this.platform;
88      }
89  
90      /**
91       * Returns the underlying connection, requested from
92       * {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}.
93       * <p>
94       * PB#beginTransaction() opens a single jdbc connection via
95  	 * PB#serviceConnectionManager().localBegin().
96  	 * If you call PB#serviceConnectionManager().getConnection() later
97  	 * it returns the already opened connection.
98  	 * The PB instance will release the used connection during
99  	 * 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 }