001/*
002 * (C) Copyright 2006-2017 Nuxeo (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.List;
024import java.util.Map;
025import java.util.concurrent.ConcurrentHashMap;
026
027import javax.naming.CompositeName;
028import javax.naming.Context;
029import javax.naming.Name;
030import javax.naming.NamingException;
031import javax.naming.Reference;
032import javax.naming.spi.NamingManager;
033import javax.resource.ResourceException;
034import javax.resource.spi.ConnectionManager;
035import javax.resource.spi.ConnectionRequestInfo;
036import javax.resource.spi.ManagedConnectionFactory;
037import javax.transaction.HeuristicMixedException;
038import javax.transaction.HeuristicRollbackException;
039import javax.transaction.InvalidTransactionException;
040import javax.transaction.NotSupportedException;
041import javax.transaction.RollbackException;
042import javax.transaction.SystemException;
043import javax.transaction.Transaction;
044import javax.transaction.TransactionManager;
045import javax.transaction.TransactionSynchronizationRegistry;
046import javax.transaction.UserTransaction;
047import javax.transaction.xa.XAException;
048import javax.transaction.xa.XAResource;
049
050import org.apache.commons.logging.Log;
051import org.apache.commons.logging.LogFactory;
052import org.apache.geronimo.connector.outbound.AbstractConnectionManager;
053import org.apache.geronimo.connector.outbound.connectionmanagerconfig.LocalTransactions;
054import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport;
055import org.apache.geronimo.connector.outbound.connectionmanagerconfig.TransactionSupport;
056import org.apache.geronimo.connector.outbound.connectionmanagerconfig.XATransactions;
057import org.apache.geronimo.transaction.manager.NamedXAResourceFactory;
058import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
059import org.apache.geronimo.transaction.manager.TransactionImpl;
060import org.apache.geronimo.transaction.manager.TransactionManagerImpl;
061import org.apache.xbean.naming.reference.SimpleReference;
062import org.nuxeo.common.logging.SequenceTracer;
063import org.nuxeo.common.utils.ExceptionUtils;
064import org.nuxeo.runtime.jtajca.NuxeoConnectionManager.ActiveMonitor;
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<>(8, 0.75f, 2);
091
092    private static final List<NuxeoContainerListener> listeners = new ArrayList<>();
093
094    private static volatile InstallContext installContext;
095
096    protected static Context rootContext;
097
098    protected static Context parentContext;
099
100    protected static String jndiPrefix = "java:comp/env/";
101
102    // @since 5.7
103    protected static final MetricRegistry registry = SharedMetricRegistries.getOrCreate(MetricsService.class.getName());
104
105    protected static final Counter rollbackCount = registry.counter(
106            MetricRegistry.name("nuxeo", "transactions", "rollbacks"));
107
108    protected static final Counter concurrentCount = registry.counter(
109            MetricRegistry.name("nuxeo", "transactions", "concurrents", "count"));
110
111    protected static final Counter concurrentMaxCount = registry.counter(
112            MetricRegistry.name("nuxeo", "transactions", "concurrents", "max"));
113
114    protected static final Timer transactionTimer = registry.timer(
115            MetricRegistry.name("nuxeo", "transactions", "duration"));
116
117    protected static final ConcurrentHashMap<Transaction, Timer.Context> timers = new ConcurrentHashMap<>();
118
119    private NuxeoContainer() {
120    }
121
122    public static class InstallContext extends Throwable {
123        private static final long serialVersionUID = 1L;
124
125        public final String threadName;
126
127        InstallContext() {
128            super("Container installation context (" + Thread.currentThread().getName() + ")");
129            threadName = Thread.currentThread().getName();
130        }
131    }
132
133    /**
134     * Install naming and bind transaction and connection management factories "by hand".
135     */
136    protected static synchronized void install() throws NamingException {
137        if (installContext != null) {
138            throw new RuntimeException("Nuxeo container already installed");
139        }
140        installContext = new InstallContext();
141        rootContext = new NamingContext();
142        parentContext = InitialContextAccessor.getInitialContext();
143        if (parentContext != null && parentContext != rootContext) {
144            installTransactionManager(parentContext);
145        } else {
146            addDeepBinding(nameOf("TransactionManager"), new Reference(TransactionManager.class.getName(),
147                    NuxeoTransactionManagerFactory.class.getName(), null));
148            installTransactionManager(rootContext);
149        }
150    }
151
152    protected static void installTransactionManager(TransactionManagerConfiguration config) throws NamingException {
153        initTransactionManager(config);
154        addDeepBinding(rootContext, new CompositeName(nameOf("TransactionManager")), getTransactionManagerReference());
155        addDeepBinding(rootContext, new CompositeName(nameOf("UserTransaction")), getUserTransactionReference());
156    }
157
158    /**
159     * Creates and installs in the container a new ConnectionManager.
160     *
161     * @param name the repository name
162     * @param config the pool configuration
163     * @return the created connection manager
164     */
165    public static synchronized ConnectionManagerWrapper installConnectionManager(
166            NuxeoConnectionManagerConfiguration config) {
167        String name = config.getName();
168        ConnectionManagerWrapper cm = connectionManagers.get(name);
169        if (cm != null) {
170            return cm;
171        }
172        cm = initConnectionManager(config);
173        // also bind it in JNDI
174        if (rootContext != null) {
175            String jndiName = nameOf("ConnectionManager/".concat(name));
176            try {
177                addDeepBinding(rootContext, new CompositeName(jndiName), getConnectionManagerReference(name));
178            } catch (NamingException e) {
179                log.error("Cannot bind in JNDI connection manager " + config.getName() + " to name " + jndiName);
180            }
181        }
182        return cm;
183    }
184
185    public static boolean isInstalled() {
186        return installContext != null;
187    }
188
189    protected static void uninstall() throws NamingException {
190        if (installContext == null) {
191            throw new RuntimeException("Nuxeo container not installed");
192        }
193        try {
194            NamingException errors = new NamingException("Cannot shutdown connection managers");
195            for (ConnectionManagerWrapper cm : connectionManagers.values()) {
196                try {
197                    cm.dispose();
198                } catch (RuntimeException cause) {
199                    errors.addSuppressed(cause);
200                }
201            }
202            if (errors.getSuppressed().length > 0) {
203                log.error("Cannot shutdown some pools", errors);
204                throw errors;
205            }
206        } finally {
207            log.trace("Uninstalling nuxeo container", installContext);
208            installContext = null;
209            rootContext = null;
210            tm = null;
211            tmRecoverable = null;
212            tmSynchRegistry = null;
213            ut = null;
214            connectionManagers.clear();
215        }
216    }
217
218    /**
219     * @since 5.8
220     */
221    public static void addListener(NuxeoContainerListener listener) {
222        synchronized (listeners) {
223            listeners.add(listener);
224        }
225        for (Map.Entry<String, ConnectionManagerWrapper> entry : connectionManagers.entrySet()) {
226            listener.handleNewConnectionManager(entry.getKey(), entry.getValue().cm);
227        }
228    }
229
230    /**
231     * @since 5.8
232     */
233    public static void removeListener(NuxeoContainerListener listener) {
234        synchronized (listeners) {
235            listeners.remove(listener);
236        }
237    }
238
239    protected static String detectJNDIPrefix(Context context) {
240        String name = context.getClass().getName();
241        if ("org.jnp.interfaces.NamingContext".equals(name)) { // JBoss
242            return "java:";
243        } else if ("org.jboss.as.naming.InitialContext".equals(name)) { // Wildfly
244            return "java:jboss/";
245        } else if ("org.mortbay.naming.local.localContextRoot".equals(name)) { // Jetty
246            return "jdbc/";
247        }
248        // Standard JEE containers (Nuxeo-Embedded, Tomcat, GlassFish,
249        // ...
250        return "java:comp/env/";
251    }
252
253    public static String nameOf(String name) {
254        return jndiPrefix.concat(name);
255    }
256
257    /**
258     * Exposes the {@link #rootContext}.
259     *
260     * @since 5.7
261     * @see https://jira.nuxeo.com/browse/NXP-10331
262     */
263    public static Context getRootContext() {
264        return rootContext;
265    }
266
267    /**
268     * Bind object in root context. Create needed sub contexts. since 5.6
269     */
270    public static void addDeepBinding(String name, Object obj) throws NamingException {
271        addDeepBinding(rootContext, new CompositeName(name), obj);
272    }
273
274    protected static void addDeepBinding(Context dir, CompositeName comp, Object obj) throws NamingException {
275        Name name = comp.getPrefix(1);
276        if (comp.size() == 1) {
277            addBinding(dir, name, obj);
278            return;
279        }
280        Context subdir;
281        try {
282            subdir = (Context) dir.lookup(name);
283        } catch (NamingException e) {
284            subdir = dir.createSubcontext(name);
285        }
286        addDeepBinding(subdir, (CompositeName) comp.getSuffix(1), obj);
287    }
288
289    protected static void addBinding(Context dir, Name name, Object obj) throws NamingException {
290        try {
291            dir.rebind(name, obj);
292        } catch (NamingException e) {
293            dir.bind(name, obj);
294        }
295    }
296
297    protected static void removeBinding(String name) throws NamingException {
298        rootContext.unbind(name);
299    }
300
301    /**
302     * Gets the transaction manager used by the container.
303     *
304     * @return the transaction manager
305     */
306    public static TransactionManager getTransactionManager() {
307        return tm;
308    }
309
310    protected static Reference getTransactionManagerReference() {
311        return new SimpleReference() {
312            private static final long serialVersionUID = 1L;
313
314            @Override
315            public Object getContent() throws NamingException {
316                return NuxeoContainer.getTransactionManager();
317            }
318        };
319    }
320
321    /**
322     * Gets the user transaction used by the container.
323     *
324     * @return the user transaction
325     */
326    public static UserTransaction getUserTransaction() {
327        return ut;
328    }
329
330    protected static Reference getUserTransactionReference() {
331        return new SimpleReference() {
332            private static final long serialVersionUID = 1L;
333
334            @Override
335            public Object getContent() throws NamingException {
336                return getUserTransaction();
337            }
338        };
339    }
340
341    /**
342     * Gets the Nuxeo connection manager used by the container.
343     *
344     * @return the connection manager
345     */
346    public static NuxeoConnectionManager getConnectionManager(String repositoryName) {
347        ConnectionManagerWrapper wrapper = connectionManagers.get(repositoryName);
348        if (wrapper == null) {
349            return null;
350        }
351        return wrapper.cm;
352    }
353
354    public static void installConnectionManager(ConnectionManagerWrapper wrapper) {
355        String name = wrapper.config.getName();
356        if (connectionManagers.containsKey(name)) {
357            log.error("Connection manager " + name + " already set up", new Exception());
358        }
359        connectionManagers.put(name, wrapper);
360        for (NuxeoContainerListener listener : listeners) {
361            listener.handleNewConnectionManager(name, wrapper.cm);
362        }
363    }
364
365    protected static Reference getConnectionManagerReference(final String name) {
366        return new SimpleReference() {
367            private static final long serialVersionUID = 1L;
368
369            @Override
370            public Object getContent() throws NamingException {
371                return getConnectionManager(name);
372            }
373        };
374    }
375
376    protected static synchronized TransactionManager initTransactionManager(TransactionManagerConfiguration config) {
377        TransactionManagerImpl impl = createTransactionManager(config);
378        tm = impl;
379        tmRecoverable = impl;
380        tmSynchRegistry = impl;
381        ut = new UserTransactionImpl(tm);
382        return tm;
383    }
384
385    protected static TransactionManagerWrapper wrapTransactionManager(TransactionManager tm) {
386        if (tm == null) {
387            return null;
388        }
389        if (tm instanceof TransactionManagerWrapper) {
390            return (TransactionManagerWrapper) tm;
391        }
392        return new TransactionManagerWrapper(tm);
393    }
394
395    public static synchronized ConnectionManagerWrapper initConnectionManager(
396            NuxeoConnectionManagerConfiguration config) {
397        NuxeoConnectionTrackingCoordinator coordinator = new NuxeoConnectionTrackingCoordinator();
398        NuxeoConnectionManager cm = createConnectionManager(coordinator, config);
399        ConnectionManagerWrapper cmw = new ConnectionManagerWrapper(coordinator, cm, config);
400        installConnectionManager(cmw);
401        return cmw;
402    }
403
404    public static synchronized void disposeConnectionManager(ConnectionManager mgr) {
405        ((ConnectionManagerWrapper) mgr).dispose();
406    }
407
408    // called by reflection from RepositoryReloader
409    public static synchronized void resetConnectionManager() {
410        RuntimeException errors = new RuntimeException("Cannot reset connection managers");
411        for (ConnectionManagerWrapper wrapper : connectionManagers.values()) {
412            try {
413                wrapper.reset();
414            } catch (RuntimeException cause) {
415                errors.addSuppressed(cause);
416            }
417        }
418        if (errors.getSuppressed().length > 0) {
419            throw errors;
420        }
421    }
422
423    public static synchronized void resetConnectionManager(String name) {
424        connectionManagers.get(name).reset();
425    }
426
427    public static <T> T lookup(String name, Class<T> type) throws NamingException {
428        if (rootContext == null) {
429            throw new NamingException("no naming context available");
430        }
431        return lookup(rootContext, name, type);
432    }
433
434    public static <T> T lookup(Context context, String name, Class<T> type) throws NamingException {
435        Object resolved;
436        try {
437            resolved = context.lookup(detectJNDIPrefix(context).concat(name));
438        } catch (NamingException cause) {
439            if (parentContext == null) {
440                throw cause;
441            }
442            return type.cast(parentContext.lookup(detectJNDIPrefix(parentContext).concat(name)));
443        }
444        if (resolved instanceof Reference) {
445            try {
446                resolved = NamingManager.getObjectInstance(resolved, new CompositeName(name), rootContext, null);
447            } catch (NamingException e) {
448                throw e;
449            } catch (Exception e) { // stupid JNDI API throws Exception
450                throw ExceptionUtils.runtimeException(e);
451            }
452        }
453        return type.cast(resolved);
454    }
455
456    protected static void installTransactionManager(Context context) throws NamingException {
457        TransactionManager actual = lookup(context, "TransactionManager", TransactionManager.class);
458        if (tm != null) {
459            return;
460        }
461        tm = actual;
462        tmRecoverable = wrapTransactionManager(tm);
463        ut = new UserTransactionImpl(tm);
464        tmSynchRegistry = (TransactionSynchronizationRegistry) tm;
465    }
466
467    protected static ConnectionManagerWrapper lookupConnectionManager(String repositoryName) throws NamingException {
468        ConnectionManager cm = lookup(rootContext, "ConnectionManager/".concat(repositoryName),
469                ConnectionManager.class);
470        if (cm instanceof ConnectionManagerWrapper) {
471            return (ConnectionManagerWrapper) cm;
472        }
473        log.warn("Connection manager not a wrapper, check your configuration");
474        throw new RuntimeException(
475                "Connection manager of " + repositoryName + " not a wrapper, check your configuration");
476    }
477
478    protected static TransactionManagerImpl createTransactionManager(TransactionManagerConfiguration config) {
479        if (config == null) {
480            config = new TransactionManagerConfiguration();
481        }
482        try {
483            return new TransactionManagerImpl(config.transactionTimeoutSeconds);
484        } catch (XAException e) {
485            // failed in recovery somewhere
486            throw new RuntimeException(e.toString(), e);
487        }
488    }
489
490    /**
491     * User transaction that uses this container's transaction manager.
492     *
493     * @since 5.6
494     */
495    public static class UserTransactionImpl implements UserTransaction {
496
497        protected final TransactionManager transactionManager;
498
499        public UserTransactionImpl(TransactionManager manager) {
500            transactionManager = manager;
501        }
502
503        @Override
504        public int getStatus() throws SystemException {
505            return transactionManager.getStatus();
506        }
507
508        @Override
509        public void setRollbackOnly() throws IllegalStateException, SystemException {
510            transactionManager.setRollbackOnly();
511        }
512
513        @Override
514        public void setTransactionTimeout(int seconds) throws SystemException {
515            transactionManager.setTransactionTimeout(seconds);
516        }
517
518        @Override
519        public void begin() throws NotSupportedException, SystemException {
520            SequenceTracer.start("tx begin", "#DarkSalmon");
521            transactionManager.begin();
522            timers.put(transactionManager.getTransaction(), transactionTimer.time());
523            concurrentCount.inc();
524            if (concurrentCount.getCount() > concurrentMaxCount.getCount()) {
525                concurrentMaxCount.inc();
526            }
527        }
528
529        @Override
530        public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException,
531                RollbackException, SecurityException, SystemException {
532            SequenceTracer.start("tx commiting", "#de6238");
533            Transaction transaction = transactionManager.getTransaction();
534            if (transaction == null) {
535                throw new IllegalStateException("No transaction associated with current thread");
536            }
537            Timer.Context timerContext = timers.remove(transaction);
538            transactionManager.commit();
539            if (timerContext != null) {
540                long elapsed = timerContext.stop();
541                SequenceTracer.stop("tx commited");
542                SequenceTracer.stop("tx end " + elapsed / 1000000 + " ms");
543            }
544            concurrentCount.dec();
545        }
546
547        @Override
548        public void rollback() throws IllegalStateException, SecurityException, SystemException {
549            SequenceTracer.mark("tx rollbacking");
550            Transaction transaction = transactionManager.getTransaction();
551            if (transaction == null) {
552                throw new IllegalStateException("No transaction associated with current thread");
553            }
554            Timer.Context timerContext = timers.remove(transaction);
555            transactionManager.rollback();
556            concurrentCount.dec();
557            if (timerContext != null) {
558                long elapsed = timerContext.stop();
559                SequenceTracer.destroy("tx rollbacked " + elapsed / 1000000 + " ms");
560            }
561            rollbackCount.inc();
562        }
563    }
564
565    /**
566     * Creates a Geronimo pooled connection manager using a Geronimo transaction manager.
567     * <p>
568     * The pool uses the transaction manager for recovery, and when using XATransactions for cache + enlist/delist.
569     *
570     * @throws NamingException
571     */
572    public static NuxeoConnectionManager createConnectionManager(NuxeoConnectionTrackingCoordinator coordinator,
573            NuxeoConnectionManagerConfiguration config) {
574        TransactionSupport transactionSupport = createTransactionSupport(config);
575        PoolingSupport poolingSupport = createPoolingSupport(config);
576        NuxeoValidationSupport validationSupport = createValidationSupport(config);
577        return new NuxeoConnectionManager(config.getActiveTimeoutMinutes() * 60 * 1000, validationSupport,
578                transactionSupport, poolingSupport, null, coordinator, tmRecoverable, config.getName(),
579                Thread.currentThread().getContextClassLoader());
580    }
581
582    protected static PoolingSupport createPoolingSupport(NuxeoConnectionManagerConfiguration config) {
583        return new NuxeoPool(config);
584    }
585
586    protected static TransactionSupport createTransactionSupport(NuxeoConnectionManagerConfiguration config) {
587        if (config.getXAMode()) {
588            // note: XATransactions -> TransactionCachingInterceptor ->
589            // ConnectorTransactionContext casts transaction to Geronimo's
590            // TransactionImpl (from TransactionManagerImpl)
591            return new XATransactions(config.getUseTransactionCaching(), config.getUseThreadCaching());
592        }
593        return LocalTransactions.INSTANCE;
594    }
595
596    protected static NuxeoValidationSupport createValidationSupport(NuxeoConnectionManagerConfiguration config) {
597        return new NuxeoValidationSupport(config.testOnBorrow, config.testOnReturn);
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)
643                throws IllegalStateException, InvalidTransactionException, SystemException {
644            tm.resume(tobj);
645        }
646
647        @Override
648        public int getStatus() throws SystemException {
649            return tm.getStatus();
650        }
651
652        @Override
653        public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException,
654                RollbackException, SecurityException, SystemException {
655            tm.commit();
656        }
657
658        @Override
659        public void begin() throws SystemException {
660            try {
661                tm.begin();
662            } catch (javax.transaction.NotSupportedException e) {
663                throw new RuntimeException(e);
664            }
665        }
666
667        @Override
668        public void recoveryError(Exception e) {
669            throw new UnsupportedOperationException();
670        }
671
672        @Override
673        public void registerNamedXAResourceFactory(NamedXAResourceFactory factory) {
674            if (!RecoverableTransactionManager.class.isAssignableFrom(tm.getClass())) {
675                throw new UnsupportedOperationException();
676            }
677            ((RecoverableTransactionManager) tm).registerNamedXAResourceFactory(factory);
678        }
679
680        @Override
681        public void unregisterNamedXAResourceFactory(String factory) {
682            if (!RecoverableTransactionManager.class.isAssignableFrom(tm.getClass())) {
683                throw new UnsupportedOperationException();
684            }
685            ((RecoverableTransactionManager) tm).unregisterNamedXAResourceFactory(factory);
686        }
687
688        @Override
689        public Transaction getTransaction() throws SystemException {
690            final Transaction tx = tm.getTransaction();
691            if (tx instanceof TransactionImpl) {
692                return tx;
693            }
694            return new TransactionImpl(null, null) {
695                @Override
696                public void commit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException,
697                        SecurityException, SystemException {
698                    tx.commit();
699                }
700
701                @Override
702                public void rollback() throws IllegalStateException, SystemException {
703                    tx.rollback();
704                }
705
706                @Override
707                public synchronized boolean enlistResource(XAResource xaRes)
708                        throws IllegalStateException, RollbackException, SystemException {
709                    return tx.enlistResource(xaRes);
710                }
711
712                @Override
713                public synchronized boolean delistResource(XAResource xaRes, int flag)
714                        throws IllegalStateException, SystemException {
715                    return super.delistResource(xaRes, flag);
716                }
717
718                @Override
719                public synchronized void setRollbackOnly() throws IllegalStateException {
720                    try {
721                        tx.setRollbackOnly();
722                    } catch (SystemException e) {
723                        throw new IllegalStateException(e);
724                    }
725                }
726
727                @Override
728                public void registerInterposedSynchronization(javax.transaction.Synchronization synchronization) {
729                    try {
730                        TransactionHelper.lookupSynchronizationRegistry()
731                                         .registerInterposedSynchronization(synchronization);
732                    } catch (NamingException e) {
733                        ;
734                    }
735                }
736            };
737        }
738    }
739
740    /**
741     * Wraps a Geronimo ConnectionManager and adds a {@link #reset} method to flush the pool.
742     */
743    public static class ConnectionManagerWrapper implements ConnectionManager {
744
745        private static final long serialVersionUID = 1L;
746
747        protected NuxeoConnectionTrackingCoordinator coordinator;
748
749        protected volatile NuxeoConnectionManager cm;
750
751        protected final NuxeoConnectionManagerConfiguration config;
752
753        public ConnectionManagerWrapper(NuxeoConnectionTrackingCoordinator coordinator, NuxeoConnectionManager cm,
754                NuxeoConnectionManagerConfiguration config) {
755            this.coordinator = coordinator;
756            this.cm = cm;
757            this.config = config;
758        }
759
760        @Override
761        public Object allocateConnection(ManagedConnectionFactory managedConnectionFactory,
762                ConnectionRequestInfo connectionRequestInfo) throws ResourceException {
763            return cm.allocateConnection(managedConnectionFactory, connectionRequestInfo);
764        }
765
766        public void reset() {
767            AbstractConnectionManager last = cm;
768            cm = createConnectionManager(coordinator, config);
769            try {
770                last.doStop();
771            } catch (Exception e) { // stupid Geronimo API throws Exception
772                throw ExceptionUtils.runtimeException(e);
773            }
774            for (NuxeoContainerListener listener : listeners) {
775                listener.handleConnectionManagerReset(config.getName(), cm);
776            }
777        }
778
779        public List<ActiveMonitor.TimeToLive> killActiveTimedoutConnections(long clock) {
780            return cm.activemonitor.killTimedoutConnections(clock);
781        }
782
783        public void dispose() {
784            for (NuxeoContainerListener listener : listeners) {
785                listener.handleConnectionManagerDispose(config.getName(), cm);
786            }
787            cm.activemonitor.cancelCleanups();
788            NuxeoContainer.connectionManagers.remove(config.getName());
789            try {
790                cm.doStop();
791            } catch (Exception e) { // stupid Geronimo API throws Exception
792                throw ExceptionUtils.runtimeException(e);
793            }
794        }
795
796        public NuxeoConnectionManagerConfiguration getConfiguration() {
797            return config;
798        }
799
800        public NuxeoConnectionManager getManager() {
801            return cm;
802        }
803
804    }
805
806    public static TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() {
807        return tmSynchRegistry;
808    }
809
810}