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