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