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