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