1 package org.apache.jetspeed.components.rdbms.ojb;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
63
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
135
136
137
138
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
177
178
179
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
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
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
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
425
426
427
428
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 }