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.connectionmanager;
023:
024: import java.io.ByteArrayOutputStream;
025: import java.io.PrintStream;
026: import java.lang.reflect.Method;
027: import java.util.ArrayList;
028: import java.util.Collection;
029: import java.util.HashMap;
030: import java.util.HashSet;
031: import java.util.Iterator;
032: import java.util.LinkedList;
033: import java.util.Map;
034: import java.util.Set;
035: import java.util.WeakHashMap;
036:
037: import javax.management.ObjectName;
038: import javax.resource.ResourceException;
039: import javax.resource.spi.ConnectionRequestInfo;
040: import javax.transaction.Synchronization;
041: import javax.transaction.SystemException;
042: import javax.transaction.Transaction;
043: import javax.transaction.TransactionManager;
044:
045: import org.jboss.ejb.EnterpriseContext;
046: import org.jboss.system.ServiceMBeanSupport;
047: import org.jboss.tm.TxUtils;
048: import org.jboss.tm.usertx.client.ServerVMClientUserTransaction;
049: import org.jboss.util.Strings;
050:
051: /**
052: * The CachedConnectionManager mbean manages associations between meta-aware objects
053: * (those accessed through interceptor chains) and connection handles, and between
054: * user transactions and connection handles. Normally there should only be one
055: * such mbean. It is called by CachedConnectionInterceptor, UserTransaction,
056: * and all BaseConnectionManager2 instances.
057: *
058: * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
059: * @author <a href="mailto:E.Guib@ceyoniq.com">Erwin Guib</a>
060: * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
061: * @version $Revision: 57189 $
062: */
063: public class CachedConnectionManager extends ServiceMBeanSupport
064: implements
065: ServerVMClientUserTransaction.UserTransactionStartedListener,
066: CachedConnectionManagerMBean {
067: private boolean specCompliant;
068:
069: protected boolean trace;
070:
071: private boolean debug;
072:
073: protected boolean error;
074:
075: private ObjectName transactionManagerServiceName;
076: private TransactionManager tm;
077:
078: /**
079: * ThreadLocal that holds current calling meta-programming aware
080: * object, used in case someone is idiotic enough to cache a
081: * connection between invocations.and want the spec required
082: * behavior of it getting hooked up to an appropriate
083: * ManagedConnection on each method invocation.
084: */
085: private final ThreadLocal currentObjects = new ThreadLocal();
086:
087: /**
088: * The variable <code>objectToConnectionManagerMap</code> holds the
089: * map of meta-aware object to set of connections it holds, used by
090: * the idiot spec compliant behavior.
091: */
092: private final Map objectToConnectionManagerMap = new HashMap();
093:
094: /**
095: * Connection stacktraces
096: */
097: private Map connectionStackTraces = new WeakHashMap();
098:
099: /**
100: * Default CachedConnectionManager managed constructor for mbeans.
101: * Remember that this mbean should be a singleton.
102: *
103: * @jmx.managed-constructor
104: */
105: public CachedConnectionManager() {
106: super ();
107: trace = log.isTraceEnabled();
108: }
109:
110: public boolean isSpecCompliant() {
111: return specCompliant;
112: }
113:
114: public void setSpecCompliant(boolean specCompliant) {
115: if (specCompliant)
116: log
117: .warn("THE SpecCompliant ATTRIBUTE IS MISNAMED SEE http://jira.jboss.com/jira/browse/JBAS-1662");
118: this .specCompliant = specCompliant;
119: }
120:
121: public boolean isDebug() {
122: return debug;
123: }
124:
125: public void setDebug(boolean value) {
126: this .debug = value;
127: }
128:
129: public boolean isError() {
130: return error;
131: }
132:
133: public void setError(boolean value) {
134: this .error = value;
135: }
136:
137: public ObjectName getTransactionManagerServiceName() {
138: return transactionManagerServiceName;
139: }
140:
141: public void setTransactionManagerServiceName(
142: ObjectName transactionManagerServiceName) {
143: this .transactionManagerServiceName = transactionManagerServiceName;
144: }
145:
146: public CachedConnectionManager getInstance() {
147: return this ;
148: }
149:
150: public int getInUseConnections() {
151: synchronized (connectionStackTraces) {
152: return connectionStackTraces.size();
153: }
154: }
155:
156: public Map listInUseConnections() {
157: synchronized (connectionStackTraces) {
158: HashMap result = new HashMap();
159: for (Iterator i = connectionStackTraces.entrySet()
160: .iterator(); i.hasNext();) {
161: Map.Entry entry = (Map.Entry) i.next();
162: Throwable stackTrace = (Throwable) entry.getValue();
163: ByteArrayOutputStream baos = new ByteArrayOutputStream();
164: PrintStream ps = new PrintStream(baos);
165: stackTrace.printStackTrace(ps);
166: result.put(entry.getKey().toString(), baos.toString());
167: }
168: return result;
169: }
170: }
171:
172: protected void startService() throws Exception {
173: tm = (TransactionManager) getServer().getAttribute(
174: transactionManagerServiceName, "TransactionManager");
175: TransactionSynchronizer.setTransactionManager(tm);
176: ServerVMClientUserTransaction.getSingleton()
177: .registerTxStartedListener(this );
178: EnterpriseContext.setUserTransactionStartedListener(this );
179: }
180:
181: protected void stopService() throws Exception {
182: ServerVMClientUserTransaction.getSingleton()
183: .unregisterTxStartedListener(this );
184: EnterpriseContext.setUserTransactionStartedListener(null);
185: }
186:
187: //Object registration for meta-aware objects (i.e. this is called by interceptors)
188:
189: /**
190: * Describe <code>pushMetaAwareObject</code> method here.
191: * PUBLIC for TESTING PURPOSES ONLY!
192: *
193: * @param rawKey an <code>Object</code> value
194: * @param unsharableResources the unsharable resources
195: * @exception ResourceException if an error occurs
196: */
197: public void pushMetaAwareObject(final Object rawKey,
198: Set unsharableResources) throws ResourceException {
199: LinkedList stack = (LinkedList) currentObjects.get();
200: if (stack == null) {
201: if (trace)
202: log.trace("new stack for key: "
203: + Strings.defaultToString(rawKey));
204: stack = new LinkedList();
205: currentObjects.set(stack);
206: } // end of if ()
207: else {
208: if (trace)
209: log.trace("old stack for key: "
210: + Strings.defaultToString(rawKey));
211: //At one time I attempted to recycle connections held over method calls.
212: //This caused problems if the other method call started a new transaction.
213: //To assure optimal use of connections, close them before calling out.
214: } // end of else
215: //check for reentrancy, reconnect if not reentrant.
216: //wrap key to be based on == rather than equals
217: KeyConnectionAssociation key = new KeyConnectionAssociation(
218: rawKey);
219: if (specCompliant && !stack.contains(key)) {
220: reconnect(key, unsharableResources);
221: }
222: stack.addLast(key);
223: }
224:
225: /**
226: * Describe <code>popMetaAwareObject</code> method here.
227: * PUBLIC for TESTING PURPOSES ONLY!
228: *
229: * @exception ResourceException if an error occurs
230: */
231: public void popMetaAwareObject(Set unsharableResources)
232: throws ResourceException {
233: LinkedList stack = (LinkedList) currentObjects.get();
234: KeyConnectionAssociation oldKey = (KeyConnectionAssociation) stack
235: .removeLast();
236: if (trace)
237: log.trace("popped object: "
238: + Strings.defaultToString(oldKey));
239: if (specCompliant) {
240: if (!stack.contains(oldKey)) {
241: disconnect(oldKey, unsharableResources);
242: } // end of if ()
243: } else if (debug) {
244: if (closeAll(oldKey.getCMToConnectionsMap()) && error)
245: throw new ResourceException(
246: "Some connections were not closed, see the log for the allocation stacktraces");
247: }
248:
249: //At one time I attempted to recycle connections held over method calls.
250: //This caused problems if the other method call started a new transaction.
251: //To assure optimal use of connections, close them before calling out.
252: }
253:
254: KeyConnectionAssociation peekMetaAwareObject() {
255: LinkedList stack = (LinkedList) currentObjects.get();
256: if (stack == null)
257: return null;
258: if (!stack.isEmpty())
259: return (KeyConnectionAssociation) stack.getLast();
260: else
261: return null;
262: }
263:
264: //ConnectionRegistration -- called by ConnectionCacheListeners (normally ConnectionManagers)
265:
266: void registerConnection(ConnectionCacheListener cm,
267: ConnectionListener cl, Object connection,
268: ConnectionRequestInfo cri) {
269: if (debug) {
270: synchronized (connectionStackTraces) {
271: connectionStackTraces.put(connection, new Throwable(
272: "STACKTRACE"));
273: }
274: }
275:
276: KeyConnectionAssociation key = peekMetaAwareObject();
277: if (trace)
278: log.trace("registering connection from " + cm
279: + ", connection : " + connection + ", key: " + key);
280: if (key == null)
281: return; //not participating properly in this management scheme.
282:
283: ConnectionRecord cr = new ConnectionRecord(cl, connection, cri);
284: Map cmToConnectionsMap = key.getCMToConnectionsMap();
285: Collection conns = (Collection) cmToConnectionsMap.get(cm);
286: if (conns == null) {
287: conns = new ArrayList();
288: cmToConnectionsMap.put(cm, conns);
289: }
290: conns.add(cr);
291: }
292:
293: void unregisterConnection(ConnectionCacheListener cm, Object c) {
294: if (debug) {
295: CloseConnectionSynchronization cas = getCloseConnectionSynchronization(false);
296: if (cas != null)
297: cas.remove(c);
298: synchronized (connectionStackTraces) {
299: connectionStackTraces.remove(c);
300: }
301: }
302:
303: KeyConnectionAssociation key = peekMetaAwareObject();
304: if (trace)
305: log.trace("unregistering connection from " + cm
306: + ", object: " + c + ", key: " + key);
307: if (key == null)
308: return; //not participating properly in this management scheme.
309:
310: Map cmToConnectionsMap = key.getCMToConnectionsMap();
311: Collection conns = (Collection) cmToConnectionsMap.get(cm);
312: if (conns == null)
313: return; // Can happen if connections are "passed" between contexts
314: for (Iterator i = conns.iterator(); i.hasNext();) {
315: if (((ConnectionRecord) i.next()).connection == c) {
316: i.remove();
317: return;
318: }
319: }
320: throw new IllegalStateException(
321: "Trying to return an unknown connection2! " + c);
322: }
323:
324: //called by UserTransaction after starting a transaction
325: public void userTransactionStarted() throws SystemException {
326: KeyConnectionAssociation key = peekMetaAwareObject();
327: if (trace)
328: log.trace("user tx started, key: " + key);
329: if (key == null)
330: return; //not participating properly in this management scheme.
331:
332: Map cmToConnectionsMap = key.getCMToConnectionsMap();
333: for (Iterator i = cmToConnectionsMap.keySet().iterator(); i
334: .hasNext();) {
335: ConnectionCacheListener cm = (ConnectionCacheListener) i
336: .next();
337: Collection conns = (Collection) cmToConnectionsMap.get(cm);
338: cm.transactionStarted(conns);
339: }
340: }
341:
342: /**
343: * The <code>reconnect</code> method gets the cmToConnectionsMap
344: * from objectToConnectionManagerMap, copies it to the key, and
345: * reconnects all the connections in it.
346: *
347: * @param key a <code>KeyConnectionAssociation</code> value
348: * @param unsharableResources a <code>Set</code> value
349: * @exception ResourceException if an error occurs
350: */
351: private void reconnect(KeyConnectionAssociation key,
352: Set unsharableResources) throws ResourceException {
353: Map cmToConnectionsMap = null;
354: synchronized (objectToConnectionManagerMap) {
355: cmToConnectionsMap = (Map) objectToConnectionManagerMap
356: .get(key);
357: if (cmToConnectionsMap == null)
358: return;
359: }
360: key.setCMToConnectionsMap(cmToConnectionsMap);
361: for (Iterator i = cmToConnectionsMap.keySet().iterator(); i
362: .hasNext();) {
363: ConnectionCacheListener cm = (ConnectionCacheListener) i
364: .next();
365: Collection conns = (Collection) cmToConnectionsMap.get(cm);
366: cm.reconnect(conns, unsharableResources);
367: }
368: }
369:
370: private void disconnect(KeyConnectionAssociation key,
371: Set unsharableResources) throws ResourceException {
372: Map cmToConnectionsMap = key.getCMToConnectionsMap();
373: if (!cmToConnectionsMap.isEmpty()) {
374: synchronized (objectToConnectionManagerMap) {
375: objectToConnectionManagerMap.put(key,
376: cmToConnectionsMap);
377: }
378: for (Iterator i = cmToConnectionsMap.keySet().iterator(); i
379: .hasNext();) {
380: ConnectionCacheListener cm = (ConnectionCacheListener) i
381: .next();
382: Collection conns = (Collection) cmToConnectionsMap
383: .get(cm);
384: cm.disconnect(conns, unsharableResources);
385: }
386: }
387: }
388:
389: private boolean closeAll(Map cmToConnectionsMap) {
390: if (debug == false)
391: return false;
392:
393: boolean unclosed = false;
394:
395: Collection connections = cmToConnectionsMap.values();
396: if (connections.size() != 0) {
397: for (Iterator i = connections.iterator(); i.hasNext();) {
398: Collection conns = (Collection) i.next();
399: for (Iterator j = conns.iterator(); j.hasNext();) {
400: Object c = ((ConnectionRecord) j.next()).connection;
401: CloseConnectionSynchronization cas = getCloseConnectionSynchronization(true);
402: if (cas == null) {
403: unclosed = true;
404: closeConnection(c);
405: } else
406: cas.add(c);
407: }
408: }
409: }
410:
411: return unclosed;
412: }
413:
414: /**
415: * Describe <code>unregisterConnectionCacheListener</code> method here.
416: * This is a shutdown method called by a connection manager. It will remove all reference
417: * to that connection manager from the cache, so cached connections from that manager
418: * will never be recoverable.
419: * Possibly this method should not exist.
420: *
421: * @param cm a <code>ConnectionCacheListener</code> value
422: */
423: void unregisterConnectionCacheListener(ConnectionCacheListener cm) {
424: if (trace)
425: log.trace("unregisterConnectionCacheListener: " + cm);
426: synchronized (objectToConnectionManagerMap) {
427: for (Iterator i = objectToConnectionManagerMap.values()
428: .iterator(); i.hasNext();) {
429: Map cmToConnectionsMap = (Map) i.next();
430: if (cmToConnectionsMap != null)
431: cmToConnectionsMap.remove(cm);
432: }
433: }
434: }
435:
436: /**
437: * The class <code>KeyConnectionAssociation</code> wraps objects so they may be used in hashmaps
438: * based on their object identity rather than equals implementation. Used for keys.
439: */
440: private final static class KeyConnectionAssociation {
441: //key
442: private final Object o;
443:
444: //map of cm to list of connections for that cm.
445: private Map cmToConnectionsMap;
446:
447: KeyConnectionAssociation(final Object o) {
448: this .o = o;
449: }
450:
451: public boolean equals(Object other) {
452: return (other instanceof KeyConnectionAssociation)
453: && o == ((KeyConnectionAssociation) other).o;
454: }
455:
456: public String toString() {
457: return Strings.defaultToString(o);
458: }
459:
460: public int hashCode() {
461: return System.identityHashCode(o);
462: }
463:
464: public void setCMToConnectionsMap(Map cmToConnectionsMap) {
465: this .cmToConnectionsMap = cmToConnectionsMap;
466: }
467:
468: public Map getCMToConnectionsMap() {
469: if (cmToConnectionsMap == null) {
470: cmToConnectionsMap = new HashMap();
471: } // end of if ()
472: return cmToConnectionsMap;
473: }
474: }
475:
476: private void closeConnection(Object c) {
477: try {
478: Throwable e;
479: synchronized (connectionStackTraces) {
480: e = (Throwable) connectionStackTraces.remove(c);
481: }
482: Method m = c.getClass().getMethod("close", new Class[] {});
483: try {
484: if (e != null)
485: log.info(
486: "Closing a connection for you. Please close them yourself: "
487: + c, e);
488: else
489: log
490: .info("Closing a connection for you. Please close them yourself: "
491: + c);
492: m.invoke(c, new Object[] {});
493: } catch (Throwable t) {
494: log
495: .info(
496: "Throwable trying to close a connection for you, please close it yourself",
497: t);
498: }
499: } catch (NoSuchMethodException nsme) {
500: log
501: .info("Could not find a close method on alleged connection objects. Please close your own connections.");
502: }
503: }
504:
505: private CloseConnectionSynchronization getCloseConnectionSynchronization(
506: boolean createIfNotFound) {
507: try {
508: Transaction tx = tm.getTransaction();
509: if (TxUtils.isActive(tx)) {
510: TransactionSynchronizer.lock(tx);
511: try {
512: CloseConnectionSynchronization cas = (CloseConnectionSynchronization) TransactionSynchronizer
513: .getCCMSynchronization(tx);
514: if (cas == null && createIfNotFound) {
515: cas = new CloseConnectionSynchronization();
516: TransactionSynchronizer
517: .registerCCMSynchronization(tx, cas);
518: }
519: return cas;
520: } finally {
521: TransactionSynchronizer.unlock(tx);
522: }
523: }
524: } catch (Throwable t) {
525: log.debug("Unable to synchronize with transaction", t);
526: }
527: return null;
528: }
529:
530: private class CloseConnectionSynchronization implements
531: Synchronization {
532: HashSet connections = new HashSet();
533: boolean closing = false;
534:
535: public CloseConnectionSynchronization() {
536: }
537:
538: public synchronized void add(Object c) {
539: if (closing)
540: return;
541: connections.add(c);
542: }
543:
544: public synchronized void remove(Object c) {
545: if (closing)
546: return;
547: connections.remove(c);
548: }
549:
550: public void beforeCompletion() {
551: }
552:
553: public void afterCompletion(int status) {
554: synchronized (this ) {
555: closing = true;
556: }
557: for (Iterator i = connections.iterator(); i.hasNext();)
558: closeConnection(i.next());
559: connections.clear(); // Help the GC
560: }
561: }
562: }
|