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