001/* 002 * Copyright (c) 2006-2013 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Florent Guillaume 011 * Julien Carsique 012 */ 013package org.nuxeo.runtime.jtajca; 014 015import java.util.ArrayList; 016import java.util.Collection; 017import java.util.HashMap; 018import java.util.List; 019import java.util.Map; 020import java.util.concurrent.ConcurrentHashMap; 021 022import javax.naming.CompositeName; 023import javax.naming.Context; 024import javax.naming.Name; 025import javax.naming.NamingException; 026import javax.naming.Reference; 027import javax.naming.spi.NamingManager; 028import javax.resource.ResourceException; 029import javax.resource.spi.ConnectionManager; 030import javax.resource.spi.ConnectionRequestInfo; 031import javax.resource.spi.ManagedConnectionFactory; 032import javax.transaction.HeuristicMixedException; 033import javax.transaction.HeuristicRollbackException; 034import javax.transaction.InvalidTransactionException; 035import javax.transaction.NotSupportedException; 036import javax.transaction.RollbackException; 037import javax.transaction.SystemException; 038import javax.transaction.Transaction; 039import javax.transaction.TransactionManager; 040import javax.transaction.TransactionSynchronizationRegistry; 041import javax.transaction.UserTransaction; 042import javax.transaction.xa.XAException; 043import javax.transaction.xa.XAResource; 044 045import org.apache.commons.logging.Log; 046import org.apache.commons.logging.LogFactory; 047import org.apache.geronimo.connector.outbound.AbstractConnectionManager; 048import org.apache.geronimo.connector.outbound.ConnectionInfo; 049import org.apache.geronimo.connector.outbound.ConnectionReturnAction; 050import org.apache.geronimo.connector.outbound.ConnectionTrackingInterceptor; 051import org.apache.geronimo.connector.outbound.GenericConnectionManager; 052import org.apache.geronimo.connector.outbound.PoolingAttributes; 053import org.apache.geronimo.connector.outbound.connectionmanagerconfig.LocalTransactions; 054import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport; 055import org.apache.geronimo.connector.outbound.connectionmanagerconfig.SinglePool; 056import org.apache.geronimo.connector.outbound.connectionmanagerconfig.TransactionSupport; 057import org.apache.geronimo.connector.outbound.connectionmanagerconfig.XATransactions; 058import org.apache.geronimo.connector.outbound.connectiontracking.ConnectionTracker; 059import org.apache.geronimo.transaction.manager.NamedXAResourceFactory; 060import org.apache.geronimo.transaction.manager.RecoverableTransactionManager; 061import org.apache.geronimo.transaction.manager.TransactionImpl; 062import org.apache.geronimo.transaction.manager.TransactionManagerImpl; 063import org.apache.xbean.naming.reference.SimpleReference; 064import org.nuxeo.common.utils.ExceptionUtils; 065import org.nuxeo.runtime.metrics.MetricsService; 066import org.nuxeo.runtime.transaction.TransactionHelper; 067 068import com.codahale.metrics.Counter; 069import com.codahale.metrics.MetricRegistry; 070import com.codahale.metrics.SharedMetricRegistries; 071import com.codahale.metrics.Timer; 072 073/** 074 * Internal helper for the Nuxeo-defined transaction manager and connection manager. 075 * <p> 076 * This code is called by the factories registered through JNDI, or by unit tests mimicking JNDI bindings. 077 */ 078public class NuxeoContainer { 079 080 protected static final Log log = LogFactory.getLog(NuxeoContainer.class); 081 082 protected static RecoverableTransactionManager tmRecoverable; 083 084 protected static TransactionManager tm; 085 086 protected static TransactionSynchronizationRegistry tmSynchRegistry; 087 088 protected static UserTransaction ut; 089 090 protected static Map<String, ConnectionManagerWrapper> connectionManagers = new ConcurrentHashMap<String, ConnectionManagerWrapper>( 091 8, 0.75f, 2); 092 093 private static final List<NuxeoContainerListener> listeners = new ArrayList<NuxeoContainerListener>(); 094 095 private static volatile InstallContext installContext; 096 097 protected static Context rootContext; 098 099 protected static Context parentContext; 100 101 protected static String jndiPrefix = "java:comp/env/"; 102 103 // @since 5.7 104 protected static final MetricRegistry registry = SharedMetricRegistries.getOrCreate(MetricsService.class.getName()); 105 106 protected static final Counter rollbackCount = registry.counter(MetricRegistry.name("nuxeo", "transactions", 107 "rollbacks")); 108 109 protected static final Counter concurrentCount = registry.counter(MetricRegistry.name("nuxeo", "transactions", 110 "concurrents", "count")); 111 112 protected static final Counter concurrentMaxCount = registry.counter(MetricRegistry.name("nuxeo", "transactions", 113 "concurrents", "max")); 114 115 protected static final Timer transactionTimer = registry.timer(MetricRegistry.name("nuxeo", "transactions", 116 "duration")); 117 118 protected static final ConcurrentHashMap<Transaction, Timer.Context> timers = new ConcurrentHashMap<Transaction, Timer.Context>(); 119 120 private NuxeoContainer() { 121 } 122 123 public static class InstallContext extends Throwable { 124 private static final long serialVersionUID = 1L; 125 126 public final String threadName; 127 128 InstallContext() { 129 super("Container installation context (" + Thread.currentThread().getName() + ")"); 130 threadName = Thread.currentThread().getName(); 131 } 132 } 133 134 /** 135 * Install naming and bind transaction and connection management factories "by hand". 136 */ 137 protected static void install() throws NamingException { 138 if (installContext != null) { 139 throw new RuntimeException("Nuxeo container already installed"); 140 } 141 installContext = new InstallContext(); 142 log.trace("Installing nuxeo container", installContext); 143 rootContext = new NamingContext(); 144 parentContext = InitialContextAccessor.getInitialContext(); 145 if (parentContext != null && parentContext != rootContext) { 146 installTransactionManager(parentContext); 147 } else { 148 addDeepBinding(nameOf("TransactionManager"), new Reference(TransactionManager.class.getName(), 149 NuxeoTransactionManagerFactory.class.getName(), null)); 150 installTransactionManager(rootContext); 151 } 152 } 153 154 protected static void installTransactionManager(TransactionManagerConfiguration config) throws NamingException { 155 initTransactionManager(config); 156 addDeepBinding(rootContext, new CompositeName(nameOf("TransactionManager")), getTransactionManagerReference()); 157 addDeepBinding(rootContext, new CompositeName(nameOf("UserTransaction")), getUserTransactionReference()); 158 } 159 160 /** 161 * Creates and installs in the container a new ConnectionManager. 162 * 163 * @param name the repository name 164 * @param config the pool configuration 165 * @return the created connection manager 166 */ 167 public static synchronized ConnectionManagerWrapper installConnectionManager( 168 NuxeoConnectionManagerConfiguration config) { 169 String name = config.getName(); 170 ConnectionManagerWrapper cm = connectionManagers.get(name); 171 if (cm != null) { 172 return cm; 173 } 174 cm = initConnectionManager(config); 175 // also bind it in JNDI 176 if (rootContext != null) { 177 String jndiName = nameOf("ConnectionManager/".concat(name)); 178 try { 179 addDeepBinding(rootContext, new CompositeName(jndiName), getConnectionManagerReference(name)); 180 } catch (NamingException e) { 181 log.error("Cannot bind in JNDI connection manager " + config.getName() + " to name " + jndiName); 182 } 183 } 184 return cm; 185 } 186 187 public static boolean isInstalled() { 188 return installContext != null; 189 } 190 191 protected static void uninstall() throws NamingException { 192 if (installContext == null) { 193 throw new RuntimeException("Nuxeo container not installed"); 194 } 195 try { 196 NamingException errors = new NamingException("Cannot shutdown connection managers"); 197 for (ConnectionManagerWrapper cm : connectionManagers.values()) { 198 try { 199 cm.dispose(); 200 } catch (RuntimeException cause) { 201 errors.addSuppressed(cause); 202 } 203 } 204 if (errors.getSuppressed().length > 0) { 205 log.error("Cannot shutdown some pools", errors); 206 throw errors; 207 } 208 } finally { 209 log.trace("Uninstalling nuxeo container", installContext); 210 installContext = null; 211 rootContext = null; 212 tm = null; 213 tmRecoverable = null; 214 tmSynchRegistry = null; 215 ut = null; 216 connectionManagers.clear(); 217 } 218 } 219 220 /** 221 * @since 5.8 222 */ 223 public static void addListener(NuxeoContainerListener listener) { 224 synchronized (listeners) { 225 listeners.add(listener); 226 } 227 for (Map.Entry<String, ConnectionManagerWrapper> entry : connectionManagers.entrySet()) { 228 listener.handleNewConnectionManager(entry.getKey(), entry.getValue().cm); 229 } 230 } 231 232 /** 233 * @since 5.8 234 */ 235 public static void removeListener(NuxeoContainerListener listener) { 236 synchronized (listeners) { 237 listeners.remove(listener); 238 } 239 } 240 241 protected static String detectJNDIPrefix(Context context) { 242 String name = context.getClass().getName(); 243 if ("org.jnp.interfaces.NamingContext".equals(name)) { // JBoss 244 return "java:"; 245 } else if ("org.jboss.as.naming.InitialContext".equals(name)) { // Wildfly 246 return "java:jboss/"; 247 } else if ("org.mortbay.naming.local.localContextRoot".equals(name)) { // Jetty 248 return "jdbc/"; 249 } 250 // Standard JEE containers (Nuxeo-Embedded, Tomcat, GlassFish, 251 // ... 252 return "java:comp/env/"; 253 } 254 255 public static String nameOf(String name) { 256 return jndiPrefix.concat(name); 257 } 258 259 /** 260 * Exposes the {@link #rootContext}. 261 * 262 * @since 5.7 263 * @see https://jira.nuxeo.com/browse/NXP-10331 264 */ 265 public static Context getRootContext() { 266 return rootContext; 267 } 268 269 /** 270 * Bind object in root context. Create needed sub contexts. since 5.6 271 */ 272 public static void addDeepBinding(String name, Object obj) throws NamingException { 273 addDeepBinding(rootContext, new CompositeName(name), obj); 274 } 275 276 protected static void addDeepBinding(Context dir, CompositeName comp, Object obj) throws NamingException { 277 Name name = comp.getPrefix(1); 278 if (comp.size() == 1) { 279 addBinding(dir, name, obj); 280 return; 281 } 282 Context subdir; 283 try { 284 subdir = (Context) dir.lookup(name); 285 } catch (NamingException e) { 286 subdir = dir.createSubcontext(name); 287 } 288 addDeepBinding(subdir, (CompositeName) comp.getSuffix(1), obj); 289 } 290 291 protected static void addBinding(Context dir, Name name, Object obj) throws NamingException { 292 try { 293 dir.rebind(name, obj); 294 } catch (NamingException e) { 295 dir.bind(name, obj); 296 } 297 } 298 299 protected static void removeBinding(String name) throws NamingException { 300 rootContext.unbind(name); 301 } 302 303 /** 304 * Gets the transaction manager used by the container. 305 * 306 * @return the transaction manager 307 */ 308 public static TransactionManager getTransactionManager() { 309 return tm; 310 } 311 312 protected static Reference getTransactionManagerReference() { 313 return new SimpleReference() { 314 private static final long serialVersionUID = 1L; 315 316 @Override 317 public Object getContent() throws NamingException { 318 return NuxeoContainer.getTransactionManager(); 319 } 320 }; 321 } 322 323 /** 324 * Gets the user transaction used by the container. 325 * 326 * @return the user transaction 327 */ 328 public static UserTransaction getUserTransaction() { 329 return ut; 330 } 331 332 protected static Reference getUserTransactionReference() { 333 return new SimpleReference() { 334 private static final long serialVersionUID = 1L; 335 336 @Override 337 public Object getContent() throws NamingException { 338 return getUserTransaction(); 339 } 340 }; 341 } 342 343 /** 344 * Gets the Nuxeo connection manager used by the container. 345 * 346 * @return the connection manager 347 */ 348 public static ConnectionManager getConnectionManager(String repositoryName) { 349 return connectionManagers.get(repositoryName); 350 } 351 352 public static void installConnectionManager(ConnectionManagerWrapper wrapper) { 353 String name = wrapper.config.getName(); 354 if (connectionManagers.containsKey(name)) { 355 log.error("Connection manager " + name + " already set up", new Exception()); 356 } 357 connectionManagers.put(name, wrapper); 358 for (NuxeoContainerListener listener : listeners) { 359 listener.handleNewConnectionManager(name, wrapper.cm); 360 } 361 } 362 363 protected static Reference getConnectionManagerReference(final String name) { 364 return new SimpleReference() { 365 private static final long serialVersionUID = 1L; 366 367 @Override 368 public Object getContent() throws NamingException { 369 return getConnectionManager(name); 370 } 371 }; 372 } 373 374 protected static synchronized TransactionManager initTransactionManager(TransactionManagerConfiguration config) { 375 TransactionManagerImpl impl = createTransactionManager(config); 376 tm = impl; 377 tmRecoverable = impl; 378 tmSynchRegistry = impl; 379 ut = new UserTransactionImpl(tm); 380 return tm; 381 } 382 383 protected static TransactionManagerWrapper wrapTransactionManager(TransactionManager tm) { 384 if (tm == null) { 385 return null; 386 } 387 if (tm instanceof TransactionManagerWrapper) { 388 return (TransactionManagerWrapper) tm; 389 } 390 return new TransactionManagerWrapper(tm); 391 } 392 393 public static synchronized ConnectionManagerWrapper initConnectionManager(NuxeoConnectionManagerConfiguration config) { 394 ConnectionTrackingCoordinator coordinator = new ConnectionTrackingCoordinator(); 395 GenericConnectionManager cm = createConnectionManager(coordinator, config); 396 ConnectionManagerWrapper cmw = new ConnectionManagerWrapper(coordinator, cm, config); 397 installConnectionManager(cmw); 398 return cmw; 399 } 400 401 public static synchronized void disposeConnectionManager(ConnectionManager mgr) { 402 ConnectionManagerWrapper wrapper = (ConnectionManagerWrapper) mgr; 403 for (NuxeoContainerListener listener : listeners) { 404 listener.handleConnectionManagerDispose(wrapper.config.getName(), wrapper.cm); 405 } 406 ((ConnectionManagerWrapper) mgr).dispose(); 407 } 408 409 public static synchronized void resetConnectionManager(String name) { 410 ConnectionManagerWrapper wrapper = connectionManagers.get(name); 411 wrapper.reset(); 412 for (NuxeoContainerListener listener : listeners) { 413 listener.handleConnectionManagerReset(name, wrapper.cm); 414 } 415 } 416 417 // called by reflection from RepositoryReloader 418 public static synchronized void resetConnectionManager() { 419 RuntimeException errors = new RuntimeException("Cannot reset connection managers"); 420 for (String name : connectionManagers.keySet()) { 421 try { 422 resetConnectionManager(name); 423 } catch (RuntimeException cause) { 424 errors.addSuppressed(cause); 425 } 426 } 427 if (errors.getSuppressed().length > 0) { 428 throw errors; 429 } 430 } 431 432 public static <T> T lookup(String name, Class<T> type) throws NamingException { 433 if (rootContext == null) { 434 throw new NamingException("no naming context available"); 435 } 436 return lookup(rootContext, name, type); 437 } 438 439 public static <T> T lookup(Context context, String name, Class<T> type) throws NamingException { 440 Object resolved; 441 try { 442 resolved = context.lookup(detectJNDIPrefix(context).concat(name)); 443 } catch (NamingException cause) { 444 if (parentContext == null) { 445 throw cause; 446 } 447 return type.cast(parentContext.lookup(detectJNDIPrefix(parentContext).concat(name))); 448 } 449 if (resolved instanceof Reference) { 450 try { 451 resolved = NamingManager.getObjectInstance(resolved, new CompositeName(name), rootContext, null); 452 } catch (NamingException e) { 453 throw e; 454 } catch (Exception e) { // stupid JNDI API throws Exception 455 throw ExceptionUtils.runtimeException(e); 456 } 457 } 458 return type.cast(resolved); 459 } 460 461 protected static void installTransactionManager(Context context) throws NamingException { 462 TransactionManager actual = lookup(context, "TransactionManager", TransactionManager.class); 463 if (tm != null) { 464 return; 465 } 466 tm = actual; 467 tmRecoverable = wrapTransactionManager(tm); 468 ut = new UserTransactionImpl(tm); 469 tmSynchRegistry = lookup(context, "TransactionSynchronizationRegistry", 470 TransactionSynchronizationRegistry.class); 471 } 472 473 protected static ConnectionManagerWrapper lookupConnectionManager(String repositoryName) throws NamingException { 474 ConnectionManager cm = lookup(rootContext, "ConnectionManager/".concat(repositoryName), ConnectionManager.class); 475 if (cm instanceof ConnectionManagerWrapper) { 476 return (ConnectionManagerWrapper) cm; 477 } 478 log.warn("Connection manager not a wrapper, check your configuration"); 479 throw new RuntimeException("Connection manager of " + repositoryName 480 + " not a wrapper, check your configuration"); 481 } 482 483 protected static TransactionManagerImpl createTransactionManager(TransactionManagerConfiguration config) { 484 if (config == null) { 485 config = new TransactionManagerConfiguration(); 486 } 487 try { 488 return new TransactionManagerImpl(config.transactionTimeoutSeconds); 489 } catch (XAException e) { 490 // failed in recovery somewhere 491 throw new RuntimeException(e.toString(), e); 492 } 493 } 494 495 /** 496 * User transaction that uses this container's transaction manager. 497 * 498 * @since 5.6 499 */ 500 public static class UserTransactionImpl implements UserTransaction { 501 502 protected final TransactionManager transactionManager; 503 504 public UserTransactionImpl(TransactionManager manager) { 505 transactionManager = manager; 506 } 507 508 @Override 509 public int getStatus() throws SystemException { 510 return transactionManager.getStatus(); 511 } 512 513 @Override 514 public void setRollbackOnly() throws IllegalStateException, SystemException { 515 transactionManager.setRollbackOnly(); 516 } 517 518 @Override 519 public void setTransactionTimeout(int seconds) throws SystemException { 520 transactionManager.setTransactionTimeout(seconds); 521 } 522 523 @Override 524 public void begin() throws NotSupportedException, SystemException { 525 transactionManager.begin(); 526 timers.put(transactionManager.getTransaction(), transactionTimer.time()); 527 concurrentCount.inc(); 528 if (concurrentCount.getCount() > concurrentMaxCount.getCount()) { 529 concurrentMaxCount.inc(); 530 } 531 } 532 533 @Override 534 public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException, 535 RollbackException, SecurityException, SystemException { 536 Timer.Context timerContext = timers.remove(transactionManager.getTransaction()); 537 transactionManager.commit(); 538 if (timerContext != null) { 539 timerContext.stop(); 540 } 541 concurrentCount.dec(); 542 } 543 544 @Override 545 public void rollback() throws IllegalStateException, SecurityException, SystemException { 546 Timer.Context timerContext = timers.remove(transactionManager.getTransaction()); 547 transactionManager.rollback(); 548 concurrentCount.dec(); 549 if (timerContext != null) { 550 timerContext.stop(); 551 } 552 rollbackCount.inc(); 553 } 554 } 555 556 /** 557 * Creates a Geronimo pooled connection manager using a Geronimo transaction manager. 558 * <p> 559 * The pool uses the transaction manager for recovery, and when using XATransactions for cache + enlist/delist. 560 * 561 * @throws NamingException 562 */ 563 public static GenericConnectionManager createConnectionManager(ConnectionTracker tracker, 564 NuxeoConnectionManagerConfiguration config) { 565 TransactionSupport transactionSupport = createTransactionSupport(config); 566 // note: XATransactions -> TransactionCachingInterceptor -> 567 // ConnectorTransactionContext casts transaction to Geronimo's 568 // TransactionImpl (from TransactionManagerImpl) 569 PoolingSupport poolingSupport = new SinglePool(config.getMaxPoolSize(), config.getMinPoolSize(), 570 config.getBlockingTimeoutMillis(), config.getIdleTimeoutMinutes(), config.getMatchOne(), 571 config.getMatchAll(), config.getSelectOneNoMatch()); 572 573 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // NuxeoContainer.class.getClassLoader(); 574 575 return new GenericConnectionManager(transactionSupport, poolingSupport, null, tracker, tmRecoverable, 576 config.getName(), classLoader); 577 } 578 579 protected static TransactionSupport createTransactionSupport(NuxeoConnectionManagerConfiguration config) { 580 if (config.getXAMode()) { 581 return new XATransactions(config.getUseTransactionCaching(), config.getUseThreadCaching()); 582 } 583 return LocalTransactions.INSTANCE; 584 } 585 586 public static class TransactionManagerConfiguration { 587 public int transactionTimeoutSeconds = 600; 588 589 public void setTransactionTimeoutSeconds(int transactionTimeoutSeconds) { 590 this.transactionTimeoutSeconds = transactionTimeoutSeconds; 591 } 592 } 593 594 /** 595 * Wraps a transaction manager for providing a dummy recoverable interface. 596 * 597 * @author matic 598 */ 599 public static class TransactionManagerWrapper implements RecoverableTransactionManager { 600 601 protected TransactionManager tm; 602 603 public TransactionManagerWrapper(TransactionManager tm) { 604 this.tm = tm; 605 } 606 607 @Override 608 public Transaction suspend() throws SystemException { 609 return tm.suspend(); 610 } 611 612 @Override 613 public void setTransactionTimeout(int seconds) throws SystemException { 614 tm.setTransactionTimeout(seconds); 615 } 616 617 @Override 618 public void setRollbackOnly() throws IllegalStateException, SystemException { 619 tm.setRollbackOnly(); 620 } 621 622 @Override 623 public void rollback() throws IllegalStateException, SecurityException, SystemException { 624 tm.rollback(); 625 } 626 627 @Override 628 public void resume(Transaction tobj) throws IllegalStateException, InvalidTransactionException, SystemException { 629 tm.resume(tobj); 630 } 631 632 @Override 633 public int getStatus() throws SystemException { 634 return tm.getStatus(); 635 } 636 637 @Override 638 public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException, 639 RollbackException, SecurityException, SystemException { 640 tm.commit(); 641 } 642 643 @Override 644 public void begin() throws SystemException { 645 try { 646 tm.begin(); 647 } catch (javax.transaction.NotSupportedException e) { 648 throw new RuntimeException(e); 649 } 650 } 651 652 @Override 653 public void recoveryError(Exception e) { 654 throw new UnsupportedOperationException(); 655 } 656 657 @Override 658 public void registerNamedXAResourceFactory(NamedXAResourceFactory factory) { 659 if (!RecoverableTransactionManager.class.isAssignableFrom(tm.getClass())) { 660 throw new UnsupportedOperationException(); 661 } 662 ((RecoverableTransactionManager) tm).registerNamedXAResourceFactory(factory); 663 } 664 665 @Override 666 public void unregisterNamedXAResourceFactory(String factory) { 667 if (!RecoverableTransactionManager.class.isAssignableFrom(tm.getClass())) { 668 throw new UnsupportedOperationException(); 669 } 670 ((RecoverableTransactionManager) tm).unregisterNamedXAResourceFactory(factory); 671 } 672 673 @Override 674 public Transaction getTransaction() throws SystemException { 675 final Transaction tx = tm.getTransaction(); 676 if (tx instanceof TransactionImpl) { 677 return tx; 678 } 679 return new TransactionImpl(null, null) { 680 @Override 681 public void commit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException, 682 SecurityException, SystemException { 683 tx.commit(); 684 } 685 686 @Override 687 public void rollback() throws IllegalStateException, SystemException { 688 tx.rollback(); 689 } 690 691 @Override 692 public synchronized boolean enlistResource(XAResource xaRes) throws IllegalStateException, 693 RollbackException, SystemException { 694 return tx.enlistResource(xaRes); 695 } 696 697 @Override 698 public synchronized boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException, 699 SystemException { 700 return super.delistResource(xaRes, flag); 701 } 702 703 @Override 704 public synchronized void setRollbackOnly() throws IllegalStateException { 705 try { 706 tx.setRollbackOnly(); 707 } catch (SystemException e) { 708 throw new IllegalStateException(e); 709 } 710 } 711 712 @Override 713 public void registerInterposedSynchronization(javax.transaction.Synchronization synchronization) { 714 try { 715 TransactionHelper.lookupSynchronizationRegistry().registerInterposedSynchronization( 716 synchronization); 717 } catch (NamingException e) {; 718 } 719 } 720 }; 721 } 722 } 723 724 public static class ConnectionTrackingCoordinator implements ConnectionTracker { 725 726 protected static class Context { 727 728 protected boolean unshareable; 729 730 protected final String threadName = Thread.currentThread().getName(); 731 732 protected final Map<ConnectionInfo, Allocation> inuse = new HashMap<ConnectionInfo, Allocation>(); 733 734 public static class AllocationErrors extends RuntimeException { 735 736 private static final long serialVersionUID = 1L; 737 738 protected AllocationErrors(Context context) { 739 super("leaked " + context.inuse + " connections in " + context.threadName); 740 for (Allocation each : context.inuse.values()) { 741 addSuppressed(each); 742 try { 743 each.info.getManagedConnectionInfo().getManagedConnection().destroy(); 744 } catch (ResourceException cause) { 745 addSuppressed(cause); 746 } 747 } 748 } 749 750 } 751 752 protected static class Allocation extends Throwable { 753 754 private static final long serialVersionUID = 1L; 755 756 public final ConnectionInfo info; 757 758 Allocation(ConnectionInfo info) { 759 super("Allocation stack trace of " + info.toString()); 760 this.info = info; 761 } 762 763 }; 764 765 @Override 766 protected void finalize() throws Throwable { 767 try { 768 checkIsEmpty(); 769 } catch (AllocationErrors cause) { 770 LogFactory.getLog(ConnectionTrackingCoordinator.class).error("cleanup errors", cause); 771 } 772 } 773 774 protected void checkIsEmpty() { 775 if (!inuse.isEmpty()) { 776 throw new AllocationErrors(this); 777 } 778 } 779 780 } 781 782 protected final ThreadLocal<Context> contextHolder = new ThreadLocal<Context>() { 783 @Override 784 protected Context initialValue() { 785 return new Context(); 786 } 787 788 }; 789 790 @Override 791 public void handleObtained(ConnectionTrackingInterceptor connectionTrackingInterceptor, 792 ConnectionInfo connectionInfo, boolean reassociate) throws ResourceException { 793 final Context context = contextHolder.get(); 794 context.inuse.put(connectionInfo, new Context.Allocation(connectionInfo)); 795 } 796 797 @Override 798 public void handleReleased(ConnectionTrackingInterceptor connectionTrackingInterceptor, 799 ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) { 800 final Context context = contextHolder.get(); 801 context.inuse.remove(connectionInfo); 802 if (context.inuse.isEmpty()) { 803 contextHolder.remove(); 804 } 805 } 806 807 @Override 808 public void setEnvironment(ConnectionInfo connectionInfo, String key) { 809 connectionInfo.setUnshareable(contextHolder.get().unshareable); 810 } 811 812 } 813 814 /** 815 * Wraps a Geronimo ConnectionManager and adds a {@link #reset} method to flush the pool. 816 */ 817 public static class ConnectionManagerWrapper implements ConnectionManager { 818 819 private static final long serialVersionUID = 1L; 820 821 protected ConnectionTrackingCoordinator coordinator; 822 823 protected AbstractConnectionManager cm; 824 825 protected final NuxeoConnectionManagerConfiguration config; 826 827 public ConnectionManagerWrapper(ConnectionTrackingCoordinator coordinator, AbstractConnectionManager cm, 828 NuxeoConnectionManagerConfiguration config) { 829 this.coordinator = coordinator; 830 this.cm = cm; 831 this.config = config; 832 } 833 834 @Override 835 public Object allocateConnection(ManagedConnectionFactory managedConnectionFactory, 836 ConnectionRequestInfo connectionRequestInfo) throws ResourceException { 837 return cm.allocateConnection(managedConnectionFactory, connectionRequestInfo); 838 } 839 840 public void reset() { 841 try { 842 cm.doStop(); 843 } catch (Exception e) { // stupid Geronimo API throws Exception 844 throw ExceptionUtils.runtimeException(e); 845 } 846 cm = createConnectionManager(coordinator, config); 847 } 848 849 public void dispose() { 850 NuxeoContainer.connectionManagers.remove(config.getName()); 851 try { 852 cm.doStop(); 853 } catch (Exception e) { // stupid Geronimo API throws Exception 854 throw ExceptionUtils.runtimeException(e); 855 } 856 } 857 858 public NuxeoConnectionManagerConfiguration getConfiguration() { 859 return config; 860 } 861 862 public Collection<ConnectionTrackingCoordinator.Context.Allocation> getCurrentThreadAllocations() { 863 return coordinator.contextHolder.get().inuse.values(); 864 } 865 866 public PoolingAttributes getPooling() { 867 return cm.getPooling(); 868 } 869 870 public void enterNoSharing() { 871 coordinator.contextHolder.get().unshareable = true; 872 } 873 874 public void exitNoSharing() { 875 coordinator.contextHolder.get().unshareable = false; 876 } 877 878 } 879 880 public static TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() { 881 return tmSynchRegistry; 882 } 883 884}