001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.resource.adapter.jdbc;
023:
024: import java.io.PrintWriter;
025: import java.sql.CallableStatement;
026: import java.sql.Connection;
027: import java.sql.PreparedStatement;
028: import java.sql.SQLException;
029: import java.sql.Savepoint;
030: import java.sql.Statement;
031: import java.util.ArrayList;
032: import java.util.Collection;
033: import java.util.HashSet;
034: import java.util.Iterator;
035: import java.util.Properties;
036: import java.util.Set;
037:
038: import javax.resource.ResourceException;
039: import javax.resource.spi.ConnectionEvent;
040: import javax.resource.spi.ConnectionEventListener;
041: import javax.resource.spi.ConnectionRequestInfo;
042: import javax.resource.spi.ManagedConnection;
043: import javax.resource.spi.ManagedConnectionMetaData;
044: import javax.resource.spi.ResourceAdapterInternalException;
045: import javax.security.auth.Subject;
046:
047: import org.jboss.logging.Logger;
048: import org.jboss.resource.JBossResourceException;
049:
050: import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
051:
052: /**
053: * BaseWrapperManagedConnection
054: *
055: * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
056: * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
057: * @version $Revision: 57189 $
058: */
059:
060: public abstract class BaseWrapperManagedConnection implements
061: ManagedConnection {
062: protected final BaseWrapperManagedConnectionFactory mcf;
063: protected final Connection con;
064: protected final Properties props;
065: private final int transactionIsolation;
066: private final boolean readOnly;
067:
068: private final Collection cels = new ArrayList();
069: private final Set handles = new HashSet();
070: private PreparedStatementCache psCache = null;
071:
072: protected final Object stateLock = new Object();
073:
074: protected boolean inManagedTransaction = false;
075: protected SynchronizedBoolean inLocalTransaction = new SynchronizedBoolean(
076: false);
077: protected boolean jdbcAutoCommit = true;
078: protected boolean underlyingAutoCommit = true;
079: protected boolean jdbcReadOnly;
080: protected boolean underlyingReadOnly;
081: protected int jdbcTransactionIsolation;
082: protected boolean destroyed = false;
083:
084: public BaseWrapperManagedConnection(
085: final BaseWrapperManagedConnectionFactory mcf,
086: final Connection con, final Properties props,
087: final int transactionIsolation, final int psCacheSize)
088: throws SQLException {
089: this .mcf = mcf;
090: this .con = con;
091: this .props = props;
092: if (psCacheSize > 0)
093: psCache = new PreparedStatementCache(psCacheSize);
094:
095: if (transactionIsolation == -1)
096: this .transactionIsolation = con.getTransactionIsolation();
097: else {
098: this .transactionIsolation = transactionIsolation;
099: con.setTransactionIsolation(transactionIsolation);
100: }
101:
102: readOnly = con.isReadOnly();
103:
104: if (mcf.getNewConnectionSQL() != null) {
105: Statement s = con.createStatement();
106: try {
107: s.execute(mcf.getNewConnectionSQL());
108: } finally {
109: s.close();
110: }
111: }
112:
113: underlyingReadOnly = readOnly;
114: jdbcReadOnly = readOnly;
115: jdbcTransactionIsolation = this .transactionIsolation;
116: }
117:
118: public void addConnectionEventListener(ConnectionEventListener cel) {
119: synchronized (cels) {
120: cels.add(cel);
121: }
122: }
123:
124: public void removeConnectionEventListener(
125: ConnectionEventListener cel) {
126: synchronized (cels) {
127: cels.remove(cel);
128: }
129: }
130:
131: public void associateConnection(Object handle)
132: throws ResourceException {
133: if (!(handle instanceof WrappedConnection))
134: throw new JBossResourceException(
135: "Wrong kind of connection handle to associate"
136: + handle);
137: ((WrappedConnection) handle).setManagedConnection(this );
138: synchronized (handles) {
139: handles.add(handle);
140: }
141: }
142:
143: public PrintWriter getLogWriter() throws ResourceException {
144: // TODO: implement this javax.resource.spi.ManagedConnection method
145: return null;
146: }
147:
148: public ManagedConnectionMetaData getMetaData()
149: throws ResourceException {
150: // TODO: implement this javax.resource.spi.ManagedConnection method
151: return null;
152: }
153:
154: public void setLogWriter(PrintWriter param1)
155: throws ResourceException {
156: // TODO: implement this javax.resource.spi.ManagedConnection method
157: }
158:
159: public void cleanup() throws ResourceException {
160: synchronized (handles) {
161: for (Iterator i = handles.iterator(); i.hasNext();) {
162: WrappedConnection lc = (WrappedConnection) i.next();
163: lc.setManagedConnection(null);
164: }
165: handles.clear();
166: }
167: //reset all the properties we know about to defaults.
168: synchronized (stateLock) {
169: jdbcAutoCommit = true;
170: jdbcReadOnly = readOnly;
171: if (jdbcTransactionIsolation != transactionIsolation) {
172: try {
173: con
174: .setTransactionIsolation(jdbcTransactionIsolation);
175: jdbcTransactionIsolation = transactionIsolation;
176: } catch (SQLException e) {
177: mcf.log
178: .warn(
179: "Error resetting transaction isolation ",
180: e);
181: }
182: }
183: }
184: }
185:
186: public Object getConnection(Subject subject,
187: ConnectionRequestInfo cri) throws ResourceException {
188: checkIdentity(subject, cri);
189: WrappedConnection lc = new WrappedConnection(this );
190: synchronized (handles) {
191: handles.add(lc);
192: }
193: return lc;
194: }
195:
196: public void destroy() throws ResourceException {
197: synchronized (stateLock) {
198: destroyed = true;
199: }
200:
201: cleanup();
202: try {
203: con.close();
204: } catch (SQLException ignored) {
205: getLog().trace("Ignored error during close: ", ignored);
206: }
207: }
208:
209: public boolean checkValid() {
210: SQLException e = mcf.isValidConnection(con);
211:
212: if (e == null)
213: // It's ok
214: return true;
215: else {
216: getLog().warn(
217: "Destroying connection that is not valid, due to the following exception: "
218: + con, e);
219: broadcastConnectionError(e);
220: return false;
221: }
222: }
223:
224: void closeHandle(WrappedConnection handle) {
225: synchronized (stateLock) {
226: if (destroyed)
227: return;
228: }
229:
230: synchronized (handles) {
231: handles.remove(handle);
232: }
233: ConnectionEvent ce = new ConnectionEvent(this ,
234: ConnectionEvent.CONNECTION_CLOSED);
235: ce.setConnectionHandle(handle);
236: Collection copy = null;
237: synchronized (cels) {
238: copy = new ArrayList(cels);
239: }
240: for (Iterator i = copy.iterator(); i.hasNext();) {
241: ConnectionEventListener cel = (ConnectionEventListener) i
242: .next();
243: cel.connectionClosed(ce);
244: }
245: }
246:
247: void connectionError(Throwable t) {
248: if (t instanceof SQLException == false
249: || mcf.isExceptionFatal((SQLException) t))
250: broadcastConnectionError(t);
251: }
252:
253: protected void broadcastConnectionError(Throwable e) {
254: synchronized (stateLock) {
255: if (destroyed) {
256: Logger log = getLog();
257: if (log.isTraceEnabled())
258: log.trace(
259: "Not broadcasting error, already destroyed "
260: + this , e);
261: return;
262: }
263: }
264:
265: Exception ex = null;
266: if (e instanceof Exception)
267: ex = (Exception) e;
268: else
269: ex = new ResourceAdapterInternalException(
270: "Unexpected error", e);
271: ConnectionEvent ce = new ConnectionEvent(this ,
272: ConnectionEvent.CONNECTION_ERROR_OCCURRED, ex);
273: Collection copy = null;
274: synchronized (cels) {
275: copy = new ArrayList(cels);
276: }
277: for (Iterator i = copy.iterator(); i.hasNext();) {
278: ConnectionEventListener cel = (ConnectionEventListener) i
279: .next();
280: try {
281: cel.connectionErrorOccurred(ce);
282: } catch (Throwable t) {
283: getLog().warn(
284: "Error notifying of connection error for listener: "
285: + cel, t);
286: }
287: }
288: }
289:
290: Connection getConnection() throws SQLException {
291: if (con == null)
292: throw new SQLException("Connection has been destroyed!!!");
293: return con;
294: }
295:
296: PreparedStatement prepareStatement(String sql, int resultSetType,
297: int resultSetConcurrency) throws SQLException {
298: if (psCache != null) {
299: PreparedStatementCache.Key key = new PreparedStatementCache.Key(
300: sql, PreparedStatementCache.Key.PREPARED_STATEMENT,
301: resultSetType, resultSetConcurrency);
302: CachedPreparedStatement cachedps = (CachedPreparedStatement) psCache
303: .get(key);
304: if (cachedps != null) {
305: if (canUse(cachedps))
306: cachedps.inUse();
307: else
308: return doPrepareStatement(sql, resultSetType,
309: resultSetConcurrency);
310: } else {
311: PreparedStatement ps = doPrepareStatement(sql,
312: resultSetType, resultSetConcurrency);
313: cachedps = new CachedPreparedStatement(ps);
314: psCache.insert(key, cachedps);
315: }
316: return cachedps;
317: } else
318: return doPrepareStatement(sql, resultSetType,
319: resultSetConcurrency);
320: }
321:
322: PreparedStatement doPrepareStatement(String sql, int resultSetType,
323: int resultSetConcurrency) throws SQLException {
324: return con.prepareStatement(sql, resultSetType,
325: resultSetConcurrency);
326: }
327:
328: CallableStatement prepareCall(String sql, int resultSetType,
329: int resultSetConcurrency) throws SQLException {
330: if (psCache != null) {
331: PreparedStatementCache.Key key = new PreparedStatementCache.Key(
332: sql, PreparedStatementCache.Key.CALLABLE_STATEMENT,
333: resultSetType, resultSetConcurrency);
334: CachedCallableStatement cachedps = (CachedCallableStatement) psCache
335: .get(key);
336: if (cachedps != null) {
337: if (canUse(cachedps))
338: cachedps.inUse();
339: else
340: return doPrepareCall(sql, resultSetType,
341: resultSetConcurrency);
342: } else {
343: CallableStatement cs = doPrepareCall(sql,
344: resultSetType, resultSetConcurrency);
345: cachedps = new CachedCallableStatement(cs);
346: psCache.insert(key, cachedps);
347: }
348: return cachedps;
349: } else
350: return doPrepareCall(sql, resultSetType,
351: resultSetConcurrency);
352: }
353:
354: CallableStatement doPrepareCall(String sql, int resultSetType,
355: int resultSetConcurrency) throws SQLException {
356: return con
357: .prepareCall(sql, resultSetType, resultSetConcurrency);
358: }
359:
360: boolean canUse(CachedPreparedStatement cachedps) {
361: // Nobody is using it so we are ok
362: if (cachedps.isInUse() == false)
363: return true;
364:
365: // Cannot reuse prepared statements in auto commit mode
366: // if will close the previous usage of the PS
367: if (underlyingAutoCommit == true)
368: return false;
369:
370: // We have been told not to share
371: return mcf.sharePS;
372: }
373:
374: protected Logger getLog() {
375: return mcf.log;
376: }
377:
378: private void checkIdentity(Subject subject,
379: ConnectionRequestInfo cri) throws ResourceException {
380: Properties newProps = mcf.getConnectionProperties(subject, cri);
381: if (!props.equals(newProps)) {
382: throw new JBossResourceException(
383: "Wrong credentials passed to getConnection!");
384: } // end of if ()
385: }
386:
387: /**
388: * The <code>checkTransaction</code> method makes sure the adapter follows the JCA
389: * autocommit contract, namely all statements executed outside a container managed transaction
390: * or a component managed transaction should be autocommitted. To avoid continually calling
391: * setAutocommit(enable) before and after container managed transactions, we keep track of the state
392: * and check it before each transactional method call.
393: */
394: void checkTransaction() throws SQLException {
395: synchronized (stateLock) {
396: if (inManagedTransaction)
397: return;
398:
399: // Check autocommit
400: if (jdbcAutoCommit != underlyingAutoCommit) {
401: con.setAutoCommit(jdbcAutoCommit);
402: underlyingAutoCommit = jdbcAutoCommit;
403: }
404: }
405:
406: if (jdbcAutoCommit == false
407: && inLocalTransaction.set(true) == false) {
408: ArrayList copy;
409: synchronized (cels) {
410: copy = new ArrayList(cels);
411: }
412: ConnectionEvent ce = new ConnectionEvent(this ,
413: ConnectionEvent.LOCAL_TRANSACTION_STARTED);
414: for (int i = 0; i < copy.size(); ++i) {
415: ConnectionEventListener cel = (ConnectionEventListener) copy
416: .get(i);
417: try {
418: cel.localTransactionStarted(ce);
419: } catch (Throwable t) {
420: getLog().trace(
421: "Error notifying of connection committed for listener: "
422: + cel, t);
423: }
424: }
425: }
426:
427: checkState();
428: }
429:
430: protected void checkState() throws SQLException {
431: synchronized (stateLock) {
432: // Check readonly
433: if (jdbcReadOnly != underlyingReadOnly) {
434: con.setReadOnly(jdbcReadOnly);
435: underlyingReadOnly = jdbcReadOnly;
436: }
437: }
438: }
439:
440: boolean isJdbcAutoCommit() {
441: return inManagedTransaction ? false : jdbcAutoCommit;
442: }
443:
444: void setJdbcAutoCommit(final boolean jdbcAutoCommit)
445: throws SQLException {
446: synchronized (stateLock) {
447: if (inManagedTransaction)
448: throw new SQLException(
449: "You cannot set autocommit during a managed transaction!");
450: this .jdbcAutoCommit = jdbcAutoCommit;
451: }
452:
453: if (jdbcAutoCommit && inLocalTransaction.set(false)) {
454: ArrayList copy;
455: synchronized (cels) {
456: copy = new ArrayList(cels);
457: }
458: ConnectionEvent ce = new ConnectionEvent(this ,
459: ConnectionEvent.LOCAL_TRANSACTION_COMMITTED);
460: for (int i = 0; i < copy.size(); ++i) {
461: ConnectionEventListener cel = (ConnectionEventListener) copy
462: .get(i);
463: try {
464: cel.localTransactionCommitted(ce);
465: } catch (Throwable t) {
466: getLog().trace(
467: "Error notifying of connection committed for listener: "
468: + cel, t);
469: }
470: }
471: }
472: }
473:
474: boolean isJdbcReadOnly() {
475: return jdbcReadOnly;
476: }
477:
478: void setJdbcReadOnly(final boolean readOnly) throws SQLException {
479: synchronized (stateLock) {
480: if (inManagedTransaction)
481: throw new SQLException(
482: "You cannot set read only during a managed transaction!");
483: this .jdbcReadOnly = readOnly;
484: }
485: }
486:
487: int getJdbcTransactionIsolation() {
488: return jdbcTransactionIsolation;
489: }
490:
491: void setJdbcTransactionIsolation(final int isolationLevel)
492: throws SQLException {
493: synchronized (stateLock) {
494: this .jdbcTransactionIsolation = isolationLevel;
495: con.setTransactionIsolation(jdbcTransactionIsolation);
496: }
497: }
498:
499: void jdbcCommit() throws SQLException {
500: synchronized (stateLock) {
501: if (inManagedTransaction)
502: throw new SQLException(
503: "You cannot commit during a managed transaction!");
504: if (jdbcAutoCommit)
505: throw new SQLException(
506: "You cannot commit with autocommit set!");
507: }
508: con.commit();
509:
510: if (inLocalTransaction.set(false)) {
511: ArrayList copy;
512: synchronized (cels) {
513: copy = new ArrayList(cels);
514: }
515: ConnectionEvent ce = new ConnectionEvent(this ,
516: ConnectionEvent.LOCAL_TRANSACTION_COMMITTED);
517: for (int i = 0; i < copy.size(); ++i) {
518: ConnectionEventListener cel = (ConnectionEventListener) copy
519: .get(i);
520: try {
521: cel.localTransactionCommitted(ce);
522: } catch (Throwable t) {
523: getLog().trace(
524: "Error notifying of connection committed for listener: "
525: + cel, t);
526: }
527: }
528: }
529: }
530:
531: void jdbcRollback() throws SQLException {
532: synchronized (stateLock) {
533: if (inManagedTransaction)
534: throw new SQLException(
535: "You cannot rollback during a managed transaction!");
536: if (jdbcAutoCommit)
537: throw new SQLException(
538: "You cannot rollback with autocommit set!");
539: }
540: con.rollback();
541:
542: if (inLocalTransaction.set(false)) {
543: ArrayList copy;
544: synchronized (cels) {
545: copy = new ArrayList(cels);
546: }
547: ConnectionEvent ce = new ConnectionEvent(this ,
548: ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK);
549: for (int i = 0; i < copy.size(); ++i) {
550: ConnectionEventListener cel = (ConnectionEventListener) copy
551: .get(i);
552: try {
553: cel.localTransactionRolledback(ce);
554: } catch (Throwable t) {
555: getLog().trace(
556: "Error notifying of connection rollback for listener: "
557: + cel, t);
558: }
559: }
560: }
561: }
562:
563: void jdbcRollback(Savepoint savepoint) throws SQLException {
564: synchronized (stateLock) {
565: if (inManagedTransaction)
566: throw new SQLException(
567: "You cannot rollback during a managed transaction!");
568: if (jdbcAutoCommit)
569: throw new SQLException(
570: "You cannot rollback with autocommit set!");
571: }
572: con.rollback(savepoint);
573: }
574:
575: int getTrackStatements() {
576: return mcf.trackStatements;
577: }
578:
579: boolean isTransactionQueryTimeout() {
580: return mcf.isTransactionQueryTimeout;
581: }
582:
583: int getQueryTimeout() {
584: return mcf.getQueryTimeout();
585: }
586:
587: protected void checkException(SQLException e)
588: throws ResourceException {
589: connectionError(e);
590: throw new JBossResourceException("SQLException", e);
591: }
592: }
|