View Javadoc

1   package org.apache.jetspeed.components.rdbms.ojb;
2   
3   /* 
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import java.sql.Connection;
21  import java.sql.SQLException;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  import org.apache.ojb.broker.OJBRuntimeException;
27  import org.apache.ojb.broker.PBKey;
28  import org.apache.ojb.broker.PersistenceBroker;
29  import org.apache.ojb.broker.PersistenceBrokerException;
30  import org.apache.ojb.broker.TransactionAbortedException;
31  import org.apache.ojb.broker.TransactionInProgressException;
32  import org.apache.ojb.broker.TransactionNotInProgressException;
33  import org.apache.ojb.broker.accesslayer.ConnectionFactory;
34  import org.apache.ojb.broker.accesslayer.ConnectionFactoryFactory;
35  import org.apache.ojb.broker.accesslayer.ConnectionManagerIF;
36  import org.apache.ojb.broker.accesslayer.LookupException;
37  import org.apache.ojb.broker.accesslayer.OJBBatchUpdateException;
38  import org.apache.ojb.broker.metadata.ConnectionPoolDescriptor;
39  import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
40  import org.apache.ojb.broker.metadata.MetadataManager;
41  import org.apache.ojb.broker.platforms.Platform;
42  import org.apache.ojb.broker.platforms.PlatformFactory;
43  import org.apache.ojb.broker.util.ClassHelper;
44  import org.apache.ojb.broker.util.batch.BatchConnection;
45  import org.apache.ojb.broker.util.logging.Logger;
46  import org.apache.ojb.broker.util.logging.LoggerFactory;
47  
48  /***
49   * Manages Connection ressources. This class is a replacement for the default
50   * ConnectionManagerImpl that comes with OJB. It differs from this class only
51   * in its way to get a connection factory. The default OJB class always uses
52   * the class configured in OJB.properties. This implementation looks up the
53   * factory configured for the given JCD first and uses the default factory
54   * class only if no class is configured in the JCD.
55   *
56   * @see ConnectionManagerIF
57   * @author Thomas Mahler
58   * @version $Id$
59   */
60  public class ConnectionManagerImpl implements ConnectionManagerIF
61  {
62      // We cannot derive from OBJ's ConnectionManagerIF because member
63      // variables are private
64      
65      private Logger log = LoggerFactory.getLogger(ConnectionManagerImpl.class);
66  
67      private PersistenceBroker broker = null;
68      private ConnectionFactory connectionFactory;
69      private JdbcConnectionDescriptor jcd;
70      private Platform platform;
71      private Connection con = null;
72      private PBKey pbKey;
73      private boolean originalAutoCommitState;
74      private boolean isInLocalTransaction;
75      private boolean batchMode;
76      private BatchConnection batchCon = null;
77  
78      private static Map connectionFactories = Collections.synchronizedMap(new HashMap());
79      
80      public ConnectionManagerImpl(PersistenceBroker broker)
81      {
82          this.broker = broker;
83          this.pbKey = broker.getPBKey();
84          this.jcd = MetadataManager.getInstance().connectionRepository().getDescriptor(pbKey);        
85          ConnectionPoolDescriptor cpd = jcd.getConnectionPoolDescriptor();        
86          if (cpd != null && cpd.getConnectionFactory() != null)
87          {
88              connectionFactory = (ConnectionFactory)connectionFactories.get(cpd.getConnectionFactory());
89              if ( connectionFactory == null )
90              {
91                  try
92                  {
93                      if (Boolean.valueOf(this.jcd.getAttribute("org.apache.jetspeed.engineScoped", "false")).booleanValue()) {
94                          ClassLoader cl = Thread.currentThread().getContextClassLoader();                
95                          try
96                          {
97                              Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
98                              connectionFactory = (ConnectionFactory)
99                                  ClassHelper.newInstance (cpd.getConnectionFactory(), true);
100                             connectionFactories.put(cpd.getConnectionFactory(), connectionFactory);
101                         }
102                         finally
103                         {
104                             Thread.currentThread().setContextClassLoader(cl);
105                             connectionFactories.put(cpd.getConnectionFactory(), connectionFactory);
106                         }
107                     }
108                     else
109                     {
110                         connectionFactory = (ConnectionFactory)
111                         ClassHelper.newInstance (cpd.getConnectionFactory(), true);
112                     }
113                 }
114                 catch (InstantiationException e)
115                 {
116                     String err = "Can't instantiate class " + cpd.getConnectionFactory();
117                     log.error(err, e);
118                     throw (IllegalStateException)(new IllegalStateException(err)).initCause(e);
119                 }
120                 catch (IllegalAccessException e)
121                 {
122                     String err = "Can't instantiate class " + cpd.getConnectionFactory();
123                     log.error(err, e);
124                     throw (IllegalStateException)(new IllegalStateException(err)).initCause(e);
125                 }
126             }
127         }
128         else 
129         {                
130             this.connectionFactory = ConnectionFactoryFactory.getInstance().createConnectionFactory();
131         }
132         this.platform = PlatformFactory.getPlatformFor(jcd);
133         /*
134         by default batch mode is not enabled and after use of a PB
135         instance, before instance was returned to pool, batch mode
136         was set to false again (PB implementation close method)
137         Be carefully in modify this behaviour, changes could cause
138         unexpected behaviour
139         */
140         setBatchMode(false);
141     }
142 
143     /***
144      * Returns the associated {@link org.apache.ojb.broker.metadata.JdbcConnectionDescriptor}
145      */
146     public JdbcConnectionDescriptor getConnectionDescriptor()
147     {
148         return jcd;
149     }
150 
151     public Platform getSupportedPlatform()
152     {
153         return this.platform;
154     }
155 
156     /***
157      * Returns the underlying connection, requested from
158      * {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}.
159      * <p>
160      * PB#beginTransaction() opens a single jdbc connection via
161 	 * PB#serviceConnectionManager().localBegin().
162 	 * If you call PB#serviceConnectionManager().getConnection() later
163 	 * it returns the already opened connection.
164 	 * The PB instance will release the used connection during
165 	 * PB#commitTransaction() or PB#abortTransaction() or PB#close().
166      * </p>
167      * <p>
168      * NOTE: Never call Connection.close() on the connection requested from the ConnectionManager.
169      * Cleanup of used connection is done by OJB itself. If you need to release a used connection
170      * call {@link #releaseConnection()}.
171      * </p>
172      */
173     public Connection getConnection() throws LookupException
174     {
175         /*
176         if the connection is not null and we are not in a local tx, we check
177         the connection state and release "dead" connections.
178         if connection is in local tx we do nothing, the dead connection will cause
179         an exception and PB instance have to handle rollback
180         */
181         if(con != null && !isInLocalTransaction() && !isAlive(con))
182         {
183             releaseConnection();
184         }
185         if (con == null)
186         {
187             if (Boolean.valueOf(this.jcd.getAttribute("org.apache.jetspeed.engineScoped", "false")).booleanValue()) {
188                 ClassLoader cl = Thread.currentThread().getContextClassLoader();
189                 try
190                 {
191                     Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
192                     con = this.connectionFactory.lookupConnection(jcd);
193                 }
194                 finally
195                 {
196                     Thread.currentThread().setContextClassLoader(cl);
197                 }
198             }
199             else
200             {
201                 con = this.connectionFactory.lookupConnection(jcd);
202             }
203             
204             if (con == null) throw new PersistenceBrokerException("Cannot get connection for " + jcd);
205             if (jcd.getUseAutoCommit() == JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE)
206             {
207                 try
208                 {
209                     this.originalAutoCommitState = con.getAutoCommit();
210                 }
211                 catch (SQLException e)
212                 {
213                     throw new PersistenceBrokerException("Cannot request autoCommit state on the connection", e);
214                 }
215             }
216             if (log.isDebugEnabled()) log.debug("Request new connection from ConnectionFactory: " + con);
217         }
218 
219         if (isBatchMode())
220         {
221             if (batchCon == null)
222             {
223                 batchCon = new BatchConnection(con, broker);
224             }
225             return batchCon;
226         }
227         else
228         {
229             return con;
230         }
231     }
232 
233     /***
234      * Start transaction on the underlying connection.
235      */
236     public void localBegin()
237     {
238         if (this.isInLocalTransaction)
239         {
240             throw new TransactionInProgressException("Connection is already in transaction");
241         }
242         Connection connection = null;
243         try
244         {
245             connection = this.getConnection();
246         }
247         catch (LookupException e)
248         {
249             /***
250              * must throw to notify user that we couldn't start a connection
251              */
252             throw new PersistenceBrokerException("Can't lookup a connection", e);
253         }
254         if (log.isDebugEnabled()) log.debug("localBegin was called for con " + connection);
255         if (jcd.getUseAutoCommit() == JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE)
256         {
257             if (log.isDebugEnabled()) log.debug("Try to change autoCommit state to 'false'");
258             platform.changeAutoCommitState(jcd, connection, false);
259         }
260         this.isInLocalTransaction = true;
261     }
262 
263     /***
264      * Call commit on the underlying connection.
265      */
266     public void localCommit()
267     {
268         if (log.isDebugEnabled()) log.debug("commit was called");
269         if (!this.isInLocalTransaction)
270         {
271             throw new TransactionNotInProgressException("Not in transaction, call begin() before commit()");
272         }
273         try
274         {
275             if (batchCon != null)
276             {
277                 batchCon.commit();
278             }
279             else if (con != null)
280             {
281                 con.commit();
282             }
283         }
284         catch (SQLException e)
285         {
286             log.error("Commit on underlying connection failed, try to rollback connection", e);
287             this.localRollback();
288             throw new TransactionAbortedException("Commit on connection failed", e);
289         }
290         finally
291         {
292             this.isInLocalTransaction = false;
293             restoreAutoCommitState();
294             this.releaseConnection();
295         }
296     }
297 
298     /***
299      * Call rollback on the underlying connection.
300      */
301     public void localRollback()
302     {
303         log.info("Rollback was called, do rollback on current connection " + con);
304         if (!this.isInLocalTransaction)
305         {
306             throw new PersistenceBrokerException("Not in transaction, cannot abort");
307         }
308         try
309         {
310             //truncate the local transaction
311             this.isInLocalTransaction = false;
312             if (batchCon != null)
313             {
314                 batchCon.rollback();
315             }
316             else if (con != null && !con.isClosed())
317             {
318                 con.rollback();
319             }
320         }
321         catch (SQLException e)
322         {
323             log.error("Rollback on the underlying connection failed", e);
324         }
325         finally
326         {
327             try
328             {
329             	restoreAutoCommitState();
330 		    }
331             catch(OJBRuntimeException ignore)
332             {
333 			    // Ignore or log exception
334 		    }
335             releaseConnection();
336         }
337     }
338 
339     /***
340      * Reset autoCommit state.
341      */
342     protected void restoreAutoCommitState()
343     {
344         try
345         {
346             if (jcd.getUseAutoCommit() == JdbcConnectionDescriptor.AUTO_COMMIT_SET_TRUE_AND_TEMPORARY_FALSE
347                     && originalAutoCommitState == true && con != null && !con.isClosed())
348             {
349                 platform.changeAutoCommitState(jcd, con, true);
350             }
351         }
352         catch (SQLException e)
353         {
354             // should never be reached
355             throw new OJBRuntimeException("Restore of connection autocommit state failed", e);
356         }
357     }
358 
359     /***
360      * Check if underlying connection was alive.
361      */
362     public boolean isAlive(Connection conn)
363     {
364         try
365         {
366             return con != null ? !con.isClosed() : false;
367         }
368         catch (SQLException e)
369         {
370             log.error("IsAlive check failed, running connection was invalid!!", e);
371             return false;
372         }
373     }
374 
375     public boolean isInLocalTransaction()
376     {
377         return this.isInLocalTransaction;
378     }
379 
380     /***
381      * Release connection to the {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}, make
382      * sure that you call the method in either case, it's the only way to free the connection.
383      */
384     public void releaseConnection()
385     {
386         if (this.con == null)
387         {
388             return;
389         }
390         if(isInLocalTransaction())
391         {
392             log.error("Release connection: connection is in local transaction, missing 'localCommit' or" +
393                     " 'localRollback' call - try to rollback the connection");
394             localRollback();
395         }
396         else
397         {
398             this.connectionFactory.releaseConnection(this.jcd, this.con);
399             this.con = null;
400             this.batchCon = null;
401         }
402     }
403 
404     /***
405      * Returns the underlying used {@link org.apache.ojb.broker.accesslayer.ConnectionFactory}
406      * implementation.
407      */
408     public ConnectionFactory getUnderlyingConnectionFactory()
409     {
410         return connectionFactory;
411     }
412 
413     /***
414      * Sets the batch mode on or off - this
415      * switch only works if you set attribute <code>batch-mode</code>
416      * in <code>jdbc-connection-descriptor</code> true and your database
417      * support batch mode.
418      *
419      * @param mode the batch mode
420      */
421     public void setBatchMode(boolean mode)
422     {
423         /*
424         arminw:
425         if batch mode was set 'false' in repository,
426         never enable it.
427         There are many users having weird problems
428         when batch mode was enabled behind the scenes
429         */
430         batchMode = mode && jcd.getBatchMode();
431     }
432 
433     /***
434      * @return the batch mode.
435      */
436     public boolean isBatchMode()
437     {
438         return batchMode && platform.supportsBatchOperations();
439     }
440 
441     /***
442      * Execute batch (if the batch mode where used).
443      */
444     public void executeBatch() throws OJBBatchUpdateException
445     {
446         if (batchCon != null)
447         {
448             try
449             {
450                 batchCon.executeBatch();
451             }
452             catch (Throwable th)
453             {
454                 throw new OJBBatchUpdateException(th);
455             }
456         }
457     }
458 
459     /***
460      * Execute batch if the number of statements in it
461      * exceeded the limit (if the batch mode where used).
462      */
463     public void executeBatchIfNecessary() throws OJBBatchUpdateException
464     {
465         if (batchCon != null)
466         {
467             try
468             {
469                 batchCon.executeBatchIfNecessary();
470             }
471             catch (Throwable th)
472             {
473                 throw new OJBBatchUpdateException(th);
474             }
475         }
476     }
477 
478     /***
479      * Clear batch (if the batch mode where used).
480      */
481     public void clearBatch()
482     {
483         if (batchCon != null)
484         {
485             batchCon.clearBatch();
486         }
487     }
488 }