1 /*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.resource.connectionmanager;
23
24 import java.io.ByteArrayOutputStream;
25 import java.io.PrintStream;
26 import java.lang.reflect.Method;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.LinkedList;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.WeakHashMap;
36
37 import javax.management.ObjectName;
38 import javax.resource.ResourceException;
39 import javax.resource.spi.ConnectionRequestInfo;
40 import javax.transaction.Synchronization;
41 import javax.transaction.SystemException;
42 import javax.transaction.Transaction;
43 import javax.transaction.TransactionManager;
44
45 import org.jboss.ejb.EnterpriseContext;
46 import org.jboss.system.ServiceMBeanSupport;
47 import org.jboss.tm.TxUtils;
48 import org.jboss.tm.usertx.client.ServerVMClientUserTransaction;
49 import org.jboss.util.Strings;
50
51 /**
52 * The CachedConnectionManager mbean manages associations between meta-aware objects
53 * (those accessed through interceptor chains) and connection handles, and between
54 * user transactions and connection handles. Normally there should only be one
55 * such mbean. It is called by CachedConnectionInterceptor, UserTransaction,
56 * and all BaseConnectionManager2 instances.
57 *
58 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
59 * @author <a href="mailto:E.Guib@ceyoniq.com">Erwin Guib</a>
60 * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
61 * @version $Revision: 74343 $
62 */
63 public class CachedConnectionManager
64 extends ServiceMBeanSupport
65 implements ServerVMClientUserTransaction.UserTransactionStartedListener,
66 CachedConnectionManagerMBean
67 {
68 private boolean specCompliant;
69
70 protected boolean trace;
71
72 private boolean debug;
73
74 protected boolean error;
75
76 private ObjectName transactionManagerServiceName;
77 private TransactionManager tm;
78
79 /**
80 * ThreadLocal that holds current calling meta-programming aware
81 * object, used in case someone is idiotic enough to cache a
82 * connection between invocations.and want the spec required
83 * behavior of it getting hooked up to an appropriate
84 * ManagedConnection on each method invocation.
85 */
86 private final ThreadLocal currentObjects = new ThreadLocal();
87
88 /**
89 * The variable <code>objectToConnectionManagerMap</code> holds the
90 * map of meta-aware object to set of connections it holds, used by
91 * the idiot spec compliant behavior.
92 */
93 private final Map objectToConnectionManagerMap = new HashMap();
94
95 /**
96 * Connection stacktraces
97 */
98 private Map connectionStackTraces = new WeakHashMap();
99
100 /**
101 * Default CachedConnectionManager managed constructor for mbeans.
102 * Remember that this mbean should be a singleton.
103 *
104 * @jmx.managed-constructor
105 */
106 public CachedConnectionManager()
107 {
108 super();
109 trace = log.isTraceEnabled();
110 }
111
112 public boolean isSpecCompliant()
113 {
114 return specCompliant;
115 }
116
117 public void setSpecCompliant(boolean specCompliant)
118 {
119 if (specCompliant)
120 log.warn("THE SpecCompliant ATTRIBUTE IS MISNAMED SEE http://jira.jboss.com/jira/browse/JBAS-1662");
121 this.specCompliant = specCompliant;
122 }
123
124 public boolean isDebug()
125 {
126 return debug;
127 }
128
129 public void setDebug(boolean value)
130 {
131 this.debug = value;
132 }
133
134 public boolean isError()
135 {
136 return error;
137 }
138
139 public void setError(boolean value)
140 {
141 this.error = value;
142 }
143
144 public ObjectName getTransactionManagerServiceName()
145 {
146 return transactionManagerServiceName;
147 }
148
149 public void setTransactionManagerServiceName(ObjectName transactionManagerServiceName)
150 {
151 this.transactionManagerServiceName = transactionManagerServiceName;
152 }
153
154 public CachedConnectionManager getInstance()
155 {
156 return this;
157 }
158
159 public int getInUseConnections()
160 {
161 synchronized (connectionStackTraces)
162 {
163 return connectionStackTraces.size();
164 }
165 }
166
167 public Map listInUseConnections()
168 {
169 synchronized (connectionStackTraces)
170 {
171 HashMap result = new HashMap();
172 for (Iterator i = connectionStackTraces.entrySet().iterator(); i.hasNext();)
173 {
174 Map.Entry entry = (Map.Entry) i.next();
175 Throwable stackTrace = (Throwable) entry.getValue();
176 ByteArrayOutputStream baos = new ByteArrayOutputStream();
177 PrintStream ps = new PrintStream(baos);
178 stackTrace.printStackTrace(ps);
179 result.put(entry.getKey().toString(), baos.toString());
180 }
181 return result;
182 }
183 }
184
185 protected void startService()
186 throws Exception
187 {
188 tm = (TransactionManager) getServer().getAttribute(transactionManagerServiceName,
189 "TransactionManager");
190 TransactionSynchronizer.setTransactionManager(tm);
191 ServerVMClientUserTransaction.getSingleton().registerTxStartedListener(this);
192 EnterpriseContext.setUserTransactionStartedListener(this);
193 }
194
195 protected void stopService() throws Exception
196 {
197 ServerVMClientUserTransaction.getSingleton().unregisterTxStartedListener(this);
198 EnterpriseContext.setUserTransactionStartedListener(null);
199 }
200
201 //Object registration for meta-aware objects (i.e. this is called by interceptors)
202
203 /**
204 * Describe <code>pushMetaAwareObject</code> method here.
205 * PUBLIC for TESTING PURPOSES ONLY!
206 *
207 * @param rawKey an <code>Object</code> value
208 * @param unsharableResources the unsharable resources
209 * @exception ResourceException if an error occurs
210 */
211 public void pushMetaAwareObject(final Object rawKey, Set unsharableResources)
212 throws ResourceException
213 {
214 LinkedList stack = (LinkedList) currentObjects.get();
215 if (stack == null)
216 {
217 if (trace)
218 log.trace("new stack for key: " + Strings.defaultToString(rawKey));
219 stack = new LinkedList();
220 currentObjects.set(stack);
221 } // end of if ()
222 else
223 {
224 if (trace)
225 log.trace("old stack for key: " + Strings.defaultToString(rawKey));
226 //At one time I attempted to recycle connections held over method calls.
227 //This caused problems if the other method call started a new transaction.
228 //To assure optimal use of connections, close them before calling out.
229 } // end of else
230 //check for reentrancy, reconnect if not reentrant.
231 //wrap key to be based on == rather than equals
232 KeyConnectionAssociation key = new KeyConnectionAssociation(rawKey);
233 if (specCompliant && !stack.contains(key))
234 {
235 reconnect(key, unsharableResources);
236 }
237 stack.addLast(key);
238 }
239
240 /**
241 * Describe <code>popMetaAwareObject</code> method here.
242 * PUBLIC for TESTING PURPOSES ONLY!
243 *
244 * @exception ResourceException if an error occurs
245 */
246 public void popMetaAwareObject(Set unsharableResources)
247 throws ResourceException
248 {
249 LinkedList stack = (LinkedList) currentObjects.get();
250 KeyConnectionAssociation oldKey = (KeyConnectionAssociation) stack.removeLast();
251 if (trace)
252 log.trace("popped object: " + Strings.defaultToString(oldKey));
253 if (specCompliant)
254 {
255 if (!stack.contains(oldKey))
256 {
257 disconnect(oldKey, unsharableResources);
258 } // end of if ()
259 }
260 else if (debug)
261 {
262 if (closeAll(oldKey.getCMToConnectionsMap()) && error)
263 throw new ResourceException("Some connections were not closed, see the log for the allocation stacktraces");
264 }
265
266 //At one time I attempted to recycle connections held over method calls.
267 //This caused problems if the other method call started a new transaction.
268 //To assure optimal use of connections, close them before calling out.
269 }
270
271 KeyConnectionAssociation peekMetaAwareObject()
272 {
273 LinkedList stack = (LinkedList) currentObjects.get();
274 if (stack == null)
275 return null;
276 if (!stack.isEmpty())
277 return (KeyConnectionAssociation) stack.getLast();
278 else
279 return null;
280 }
281
282 //ConnectionRegistration -- called by ConnectionCacheListeners (normally ConnectionManagers)
283
284 void registerConnection(ConnectionCacheListener cm, ConnectionListener cl, Object connection, ConnectionRequestInfo cri)
285 {
286 if (debug)
287 {
288 synchronized (connectionStackTraces)
289 {
290 connectionStackTraces.put(connection, new Throwable("STACKTRACE"));
291 }
292 }
293
294 KeyConnectionAssociation key = peekMetaAwareObject();
295 if (trace)
296 log.trace("registering connection from " + cm + ", connection : " + connection + ", key: " + key);
297 if (key == null)
298 return; //not participating properly in this management scheme.
299
300 ConnectionRecord cr = new ConnectionRecord(cl, connection, cri);
301 Map cmToConnectionsMap = key.getCMToConnectionsMap();
302 Collection conns = (Collection) cmToConnectionsMap.get(cm);
303 if (conns == null)
304 {
305 conns = new ArrayList();
306 cmToConnectionsMap.put(cm, conns);
307 }
308 conns.add(cr);
309 }
310
311 void unregisterConnection(ConnectionCacheListener cm, Object c)
312 {
313 if (debug)
314 {
315 CloseConnectionSynchronization cas = getCloseConnectionSynchronization(false);
316 if (cas != null)
317 cas.remove(c);
318 synchronized (connectionStackTraces)
319 {
320 connectionStackTraces.remove(c);
321 }
322 }
323
324 KeyConnectionAssociation key = peekMetaAwareObject();
325 if (trace)
326 log.trace("unregistering connection from " + cm + ", object: " + c + ", key: " + key);
327 if (key == null)
328 return; //not participating properly in this management scheme.
329
330 Map cmToConnectionsMap = key.getCMToConnectionsMap();
331 Collection conns = (Collection) cmToConnectionsMap.get(cm);
332 if (conns == null)
333 return; // Can happen if connections are "passed" between contexts
334 for (Iterator i = conns.iterator(); i.hasNext();)
335 {
336 if (((ConnectionRecord) i.next()).connection == c)
337 {
338 i.remove();
339 return;
340 }
341 }
342 throw new IllegalStateException("Trying to return an unknown connection2! " + c);
343 }
344
345 //called by UserTransaction after starting a transaction
346 public void userTransactionStarted()
347 throws SystemException
348 {
349 KeyConnectionAssociation key = peekMetaAwareObject();
350 if (trace)
351 log.trace("user tx started, key: " + key);
352 if (key == null)
353 return; //not participating properly in this management scheme.
354
355 Map cmToConnectionsMap = key.getCMToConnectionsMap();
356 Iterator cmToConnectionsMapIterator = cmToConnectionsMap.entrySet().iterator();
357 while (cmToConnectionsMapIterator.hasNext())
358 {
359 Map.Entry cmToConnectionsMapEntry = (Map.Entry)cmToConnectionsMapIterator.next();
360 ConnectionCacheListener cm = (ConnectionCacheListener) cmToConnectionsMapEntry.getKey();
361 Collection conns = (Collection) cmToConnectionsMapEntry.getValue();
362 cm.transactionStarted(conns);
363 }
364 }
365
366 /**
367 * The <code>reconnect</code> method gets the cmToConnectionsMap
368 * from objectToConnectionManagerMap, copies it to the key, and
369 * reconnects all the connections in it.
370 *
371 * @param key a <code>KeyConnectionAssociation</code> value
372 * @param unsharableResources a <code>Set</code> value
373 * @exception ResourceException if an error occurs
374 */
375 private void reconnect(KeyConnectionAssociation key, Set unsharableResources)
376 throws ResourceException
377 {
378 Map cmToConnectionsMap = null;
379 synchronized (objectToConnectionManagerMap)
380 {
381 cmToConnectionsMap = (Map) objectToConnectionManagerMap.get(key);
382 if (cmToConnectionsMap == null)
383 return;
384 }
385 key.setCMToConnectionsMap(cmToConnectionsMap);
386 Iterator cmToConnectionsMapIterator = cmToConnectionsMap.entrySet().iterator();
387 while (cmToConnectionsMapIterator.hasNext())
388 {
389 Map.Entry cmToConnectionsMapEntry = (Map.Entry)cmToConnectionsMapIterator.next();
390 ConnectionCacheListener cm = (ConnectionCacheListener) cmToConnectionsMapEntry.getKey();
391 Collection conns = (Collection) cmToConnectionsMapEntry.getValue();
392 cm.reconnect(conns, unsharableResources);
393 }
394 }
395
396 private void disconnect(KeyConnectionAssociation key, Set unsharableResources)
397 throws ResourceException
398 {
399 Map cmToConnectionsMap = key.getCMToConnectionsMap();
400 if (!cmToConnectionsMap.isEmpty())
401 {
402 synchronized (objectToConnectionManagerMap)
403 {
404 objectToConnectionManagerMap.put(key, cmToConnectionsMap);
405 }
406 Iterator cmToConnectionsMapIterator = cmToConnectionsMap.entrySet().iterator();
407 while (cmToConnectionsMapIterator.hasNext())
408 {
409 Map.Entry cmToConnectionsMapEntry = (Map.Entry)cmToConnectionsMapIterator.next();
410 ConnectionCacheListener cm = (ConnectionCacheListener) cmToConnectionsMapEntry.getKey();
411 Collection conns = (Collection) cmToConnectionsMapEntry.getValue();
412 cm.disconnect(conns, unsharableResources);
413 }
414 }
415 }
416
417 private boolean closeAll(Map cmToConnectionsMap)
418 {
419 if (debug == false)
420 return false;
421
422 boolean unclosed = false;
423
424 Collection connections = cmToConnectionsMap.values();
425 if (connections.size() != 0)
426 {
427 for (Iterator i = connections.iterator(); i.hasNext();)
428 {
429 Collection conns = (Collection) i.next();
430 for (Iterator j = conns.iterator(); j.hasNext();)
431 {
432 Object c = ((ConnectionRecord) j.next()).connection;
433 CloseConnectionSynchronization cas = getCloseConnectionSynchronization(true);
434 if (cas == null)
435 {
436 unclosed = true;
437 closeConnection(c);
438 }
439 else
440 cas.add(c);
441 }
442 }
443 }
444
445 return unclosed;
446 }
447
448 /**
449 * Describe <code>unregisterConnectionCacheListener</code> method here.
450 * This is a shutdown method called by a connection manager. It will remove all reference
451 * to that connection manager from the cache, so cached connections from that manager
452 * will never be recoverable.
453 * Possibly this method should not exist.
454 *
455 * @param cm a <code>ConnectionCacheListener</code> value
456 */
457 void unregisterConnectionCacheListener(ConnectionCacheListener cm)
458 {
459 if (trace)
460 log.trace("unregisterConnectionCacheListener: " + cm);
461 synchronized (objectToConnectionManagerMap)
462 {
463 for (Iterator i = objectToConnectionManagerMap.values().iterator(); i.hasNext();)
464 {
465 Map cmToConnectionsMap = (Map) i.next();
466 if (cmToConnectionsMap != null)
467 cmToConnectionsMap.remove(cm);
468 }
469 }
470 }
471
472 /**
473 * The class <code>KeyConnectionAssociation</code> wraps objects so they may be used in hashmaps
474 * based on their object identity rather than equals implementation. Used for keys.
475 */
476 private final static class KeyConnectionAssociation
477 {
478 //key
479 private final Object o;
480
481 //map of cm to list of connections for that cm.
482 private Map cmToConnectionsMap;
483
484 KeyConnectionAssociation(final Object o)
485 {
486 this.o = o;
487 }
488
489 public boolean equals(Object other)
490 {
491 return (other instanceof KeyConnectionAssociation) && o == ((KeyConnectionAssociation) other).o;
492 }
493
494 public String toString()
495 {
496 return Strings.defaultToString(o);
497 }
498
499 public int hashCode()
500 {
501 return System.identityHashCode(o);
502 }
503
504 public void setCMToConnectionsMap(Map cmToConnectionsMap)
505 {
506 this.cmToConnectionsMap = cmToConnectionsMap;
507 }
508
509 public Map getCMToConnectionsMap()
510 {
511 if (cmToConnectionsMap == null)
512 {
513 cmToConnectionsMap = new HashMap();
514 } // end of if ()
515 return cmToConnectionsMap;
516 }
517 }
518
519 private void closeConnection(Object c)
520 {
521 try
522 {
523 Throwable e;
524 synchronized (connectionStackTraces)
525 {
526 e = (Throwable) connectionStackTraces.remove(c);
527 }
528 Method m = c.getClass().getMethod("close", new Class[]{});
529 try
530 {
531 if (e != null)
532 log.info("Closing a connection for you. Please close them yourself: " + c, e);
533 else
534 log.info("Closing a connection for you. Please close them yourself: " + c);
535 m.invoke(c, new Object[]{});
536 }
537 catch (Throwable t)
538 {
539 log.info("Throwable trying to close a connection for you, please close it yourself", t);
540 }
541 }
542 catch (NoSuchMethodException nsme)
543 {
544 log.info("Could not find a close method on alleged connection objects. Please close your own connections.");
545 }
546 }
547
548 private CloseConnectionSynchronization getCloseConnectionSynchronization(boolean createIfNotFound)
549 {
550 try
551 {
552 Transaction tx = tm.getTransaction();
553 if (tx != null)
554 {
555 TransactionSynchronizer.lock(tx);
556 try
557 {
558 CloseConnectionSynchronization cas = (CloseConnectionSynchronization) TransactionSynchronizer.getCCMSynchronization(tx);
559 if (cas == null && createIfNotFound && TxUtils.isActive(tx))
560 {
561 cas = new CloseConnectionSynchronization();
562 TransactionSynchronizer.registerCCMSynchronization(tx, cas);
563 }
564 return cas;
565 }
566 finally
567 {
568 TransactionSynchronizer.unlock(tx);
569 }
570 }
571 }
572 catch (Throwable t)
573 {
574 log.debug("Unable to synchronize with transaction", t);
575 }
576 return null;
577 }
578
579 private class CloseConnectionSynchronization implements Synchronization
580 {
581 HashSet connections = new HashSet();
582 boolean closing = false;
583
584 public CloseConnectionSynchronization()
585 {
586 }
587
588 public synchronized void add(Object c)
589 {
590 if (closing)
591 return;
592 connections.add(c);
593 }
594
595 public synchronized void remove(Object c)
596 {
597 if (closing)
598 return;
599 connections.remove(c);
600 }
601
602 public void beforeCompletion()
603 {
604 }
605
606 public void afterCompletion(int status)
607 {
608 synchronized (this)
609 {
610 closing = true;
611 }
612 for (Iterator i = connections.iterator(); i.hasNext();)
613 closeConnection(i.next());
614 connections.clear(); // Help the GC
615 }
616 }
617 }