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 }