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}