org.eclipse.ecr.runtime.jtajca.NuxeoContainer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ecr.runtime.jtajca.NuxeoContainer.java

Source

/*
 * Copyright (c) 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Florent Guillaume, jcarsique
 */

package org.eclipse.ecr.runtime.jtajca;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.connector.outbound.AbstractConnectionManager;
import org.apache.geronimo.connector.outbound.GenericConnectionManager;
import org.apache.geronimo.connector.outbound.SubjectSource;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PartitionedPool;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.TransactionSupport;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.XATransactions;
import org.apache.geronimo.connector.outbound.connectiontracking.ConnectionTrackingCoordinator;
import org.apache.geronimo.transaction.manager.NamedXAResourceFactory;
import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
import org.apache.geronimo.transaction.manager.TransactionManagerImpl;
import org.apache.xbean.naming.reference.SimpleReference;
import org.eclipse.ecr.runtime.api.InitialContextAccessor;

/**
 * Internal helper for the Nuxeo-defined transaction manager and connection
 * manager.
 * <p>
 * This code is called by the factories registered through JNDI, or by unit
 * tests mimicking JNDI bindings.
 */
public class NuxeoContainer {

    protected static final Log log = LogFactory.getLog(NuxeoContainer.class);

    public static final String JNDI_TRANSACTION_MANAGER = "java:comp/TransactionManager";

    public static final String JNDI_USER_TRANSACTION = "java:comp/UserTransaction";

    public static final String JNDI_NUXEO_CONNECTION_MANAGER = "java:comp/NuxeoConnectionManager";

    private static TransactionManagerWrapper transactionManager;

    private static final UserTransaction userTransaction = new UserTransactionImpl();

    private static ConnectionManagerWrapper connectionManager;

    private static InstallContext installContext;

    private static Context parentContext;

    private static Map<String, Object> parentEnvironment = new HashMap<String, Object>();

    protected static Context rootContext;

    private NuxeoContainer() {
    }

    public static class InstallContext extends Throwable {
        private static final long serialVersionUID = 1L;

        public final String threadName;

        InstallContext() {
            super("Container installation context (" + Thread.currentThread().getName() + ")");
            this.threadName = Thread.currentThread().getName();
        }
    }

    /**
     * Install naming and bind transaction and connection management factories
     * "by hand".
     */
    public static synchronized void install() throws NamingException {
        if (installContext != null) {
            throw new Error("Nuxeo container already installed");
        }
        install(new TransactionManagerConfiguration(), new ConnectionManagerConfiguration());
    }

    /**
     * Install transaction and connection management "by hand" if the container
     * didn't do it using file-based configuration. Binds the names in JNDI.
     *
     * @param txconfig the transaction manager configuration
     * @param cmconfig the connection manager configuration
     *
     * @since 5.4.2
     */
    public static synchronized void install(TransactionManagerConfiguration txconfig,
            ConnectionManagerConfiguration cmconfig) throws NamingException {
        installNaming();
        transactionManager = lookupTransactionManager();
        if (transactionManager == null) {
            initTransactionManager(txconfig);
            addDeepBinding(rootContext, new CompositeName(JNDI_TRANSACTION_MANAGER),
                    getTransactionManagerReference());
            addDeepBinding(rootContext, new CompositeName(JNDI_USER_TRANSACTION), getUserTransactionReference());
        }
        connectionManager = lookupConnectionManager();
        if (connectionManager == null) {
            initConnectionManager(cmconfig);
            addDeepBinding(rootContext, new CompositeName(JNDI_NUXEO_CONNECTION_MANAGER),
                    getConnectionManagerReference());
        }
    }

    public static synchronized boolean isInstalled() {
        return installContext != null;
    }

    public static synchronized InstallContext getInstallContext() {
        return installContext;
    }

    public static synchronized void uninstall() throws NamingException {
        if (installContext == null) {
            throw new Error("Nuxeo container not installed");
        }
        try {
            removeBinding(JNDI_TRANSACTION_MANAGER);
            removeBinding(JNDI_USER_TRANSACTION);
            removeBinding(JNDI_NUXEO_CONNECTION_MANAGER);
        } catch (Exception e) {
            // do nothing
        } finally {
            transactionManager = null;
            connectionManager = null;
        }
        uninstallNaming();
        transactionManager = null;
        connectionManager = null;
        parentContext = null;
        rootContext = null;
    }

    /**
     * setup root naming context and install initial naming context factory
     *
     * @since 5.6
     */
    public static synchronized void installNaming() throws NamingException {
        installContext = new InstallContext();
        log.trace("Installing nuxeo container", installContext);
        setupRootContext();
        setAsInitialContext();
    }

    /**
     * release naming context and revert settings
     *
     * @since 5.6
     */
    public static synchronized void uninstallNaming() {
        log.trace("Uninstalling nuxeo container", installContext);
        installContext = null;
        parentContext = null;
        rootContext = null;
        revertSetAsInitialContext();
    }

    /**
     * setup root context, use the system defined context if writable, use nuxeo
     * context unless
     *
     * @since 5.6
     */
    protected static void setupRootContext() throws NamingException {
        parentContext = InitialContextAccessor.getInitialContext();
        if (parentContext != null) {
            if (InitialContextAccessor.isWritable(parentContext)) {
                rootContext = parentContext;
                return;
            }
            rootContext = new NamingContextFacade(parentContext);
            log.warn("Chaining naming spaces, can break your application server");
        }
        rootContext = new NamingContext();
    }

    /**
     * set naming context factory to nuxeo implementation, backup original
     * settings
     *
     * @since 5.6
     */
    protected static void setAsInitialContext() {
        // Preserve current set system props
        String key = Context.INITIAL_CONTEXT_FACTORY;
        parentEnvironment.put(key, System.getProperty(key));
        key = Context.URL_PKG_PREFIXES;
        parentEnvironment.put(key, System.getProperty(key));

        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, NamingContextFactory.class.getName());
        System.setProperty(Context.URL_PKG_PREFIXES, "org.eclipse.ecr.runtime.jtajca");
    }

    /**
     * revert naming factory to original settings
     *
     * @since 5.6
     */
    protected static void revertSetAsInitialContext() {
        Iterator<Entry<String, Object>> iterator = parentEnvironment.entrySet().iterator();
        Properties props = System.getProperties();

        while (iterator.hasNext()) {
            Entry<String, Object> entry = iterator.next();
            iterator.remove();
            String key = entry.getKey();
            String value = (String) entry.getValue();
            if (value == null) {
                props.remove(key);
            } else {
                props.setProperty(key, value);
            }
        }
    }

    /**
     * Bind object in root context. Create needed sub contexts.
     *
     * since 5.6
     */
    public static void addDeepBinding(String name, Object obj) throws InvalidNameException, NamingException {
        addDeepBinding(rootContext, new CompositeName(name), obj);
    }

    protected static void addDeepBinding(Context dir, CompositeName comp, Object obj) throws NamingException {
        Name name = comp.getPrefix(1);
        if (comp.size() == 1) {
            addBinding(dir, name, obj);
            return;
        }
        Context subdir;
        try {
            subdir = (Context) dir.lookup(name);
        } catch (NamingException e) {
            subdir = dir.createSubcontext(name);
        }
        addDeepBinding(subdir, (CompositeName) comp.getSuffix(1), obj);
    }

    protected static void addBinding(Context dir, Name name, Object obj) throws NamingException {
        try {
            dir.rebind(name, obj);
        } catch (NamingException e) {
            dir.bind(name, obj);
        }
    }

    protected static void removeBinding(String name) throws NamingException {
        rootContext.unbind(name);
    }

    @SuppressWarnings("unchecked")
    protected static <T> T resolveBinding(String name) throws NamingException {
        InitialContext ctx = new InitialContext();
        try {
            return (T) ctx.lookup("java:comp/" + name);
        } catch (NamingException compe) {
            try {
                return (T) ctx.lookup("java:comp/env/" + name);
            } catch (NamingException enve) {
                return (T) ctx.lookup(name);
            }
        }
    }

    /**
     * Gets the transaction manager used by the container.
     *
     * @return the transaction manager
     */
    public static TransactionManager getTransactionManager() {
        return transactionManager;
    }

    protected static Reference getTransactionManagerReference() {
        return new SimpleReference() {
            private static final long serialVersionUID = 1L;

            public Object getContent() throws NamingException {
                return NuxeoContainer.getTransactionManager();
            }
        };
    }

    /**
     * Gets the user transaction used by the container.
     *
     * @return the user transaction
     */
    public static UserTransaction getUserTransaction() throws NamingException {
        return userTransaction;
    }

    protected static Reference getUserTransactionReference() {
        return new SimpleReference() {
            private static final long serialVersionUID = 1L;

            public Object getContent() throws NamingException {
                return NuxeoContainer.getUserTransaction();
            }
        };
    }

    /**
     * Gets the Nuxeo connection manager used by the container.
     *
     * @return the connection manager
     */
    public static ConnectionManager getConnectionManager() {
        return connectionManager;
    }

    protected static Reference getConnectionManagerReference() {
        return new SimpleReference() {
            private static final long serialVersionUID = 1L;

            public Object getContent() throws NamingException {
                return NuxeoContainer.getConnectionManager();
            }
        };
    }

    public static synchronized void initTransactionManager(TransactionManagerConfiguration config)
            throws NamingException {
        TransactionManager tm = createTransactionManager(config);
        transactionManager = new TransactionManagerWrapper(tm);
    }

    protected static TransactionManagerWrapper lookupTransactionManager() {
        TransactionManager tm;
        try {
            tm = resolveBinding("TransactionManager");
        } catch (NamingException e) {
            return null;
        }
        if (tm instanceof TransactionManagerWrapper) {
            return (TransactionManagerWrapper) tm;
        }
        return new TransactionManagerWrapper(tm);
    }

    public static synchronized void initConnectionManager(ConnectionManagerConfiguration config)
            throws NamingException {
        GenericConnectionManager cm = createConnectionManager(config);
        connectionManager = new ConnectionManagerWrapper(cm, config);
    }

    public static synchronized void resetConnectionManager() throws Exception {
        ConnectionManagerWrapper cm = connectionManager;
        if (cm == null) {
            return;
        }
        cm.reset();
    }

    protected static ConnectionManagerWrapper lookupConnectionManager() {
        ConnectionManager cm;
        try {
            cm = resolveBinding("NuxeoConnectionManager");
        } catch (NamingException e) {
            return null;
        }
        if (cm instanceof ConnectionManagerWrapper) {
            return (ConnectionManagerWrapper) cm;
        }
        log.warn("Connection manager not a wrapper, check your configuration");
        return null;
    }

    protected static TransactionManager createTransactionManager(TransactionManagerConfiguration config) {
        try {
            return new TransactionManagerImpl(config.transactionTimeoutSeconds);
        } catch (Exception e) {
            // failed in recovery somewhere
            throw new RuntimeException(e.toString(), e);
        }
    }

    /**
     * User transaction that uses this container's transaction manager.
     *
     * @since 5.6
     */
    public static class UserTransactionImpl implements UserTransaction {

        protected boolean checked;

        protected void check() throws SystemException {
            if (transactionManager != null) {
                return;
            }
            if (!checked) {
                checked = true;
                transactionManager = lookupTransactionManager();
            }
            if (transactionManager == null) {
                throw new SystemException("No active transaction manager");
            }
        }

        @Override
        public int getStatus() throws SystemException {
            check();
            return transactionManager.getStatus();
        }

        @Override
        public void setRollbackOnly() throws IllegalStateException, SystemException {
            check();
            transactionManager.setRollbackOnly();
        }

        @Override
        public void setTransactionTimeout(int seconds) throws SystemException {
            check();
            transactionManager.setTransactionTimeout(seconds);
        }

        @Override
        public void begin() throws NotSupportedException, SystemException {
            check();
            transactionManager.begin();
        }

        @Override
        public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException,
                RollbackException, SecurityException, SystemException {
            check();
            transactionManager.commit();
        }

        @Override
        public void rollback() throws IllegalStateException, SecurityException, SystemException {
            check();
            transactionManager.rollback();
        }
    }

    /**
     * Creates a Geronimo pooled connection manager using a Geronimo transaction
     * manager.
     * <p>
     * The pool uses the transaction manager for recovery, and when using
     * XATransactions for cache + enlist/delist.
     *
     * @throws NamingException
     */
    protected static GenericConnectionManager createConnectionManager(ConnectionManagerConfiguration config)
            throws NamingException {
        TransactionSupport transactionSupport = new XATransactions(config.useTransactionCaching,
                config.useThreadCaching);
        // note: XATransactions -> TransactionCachingInterceptor ->
        // ConnectorTransactionContext casts transaction to Geronimo's
        // TransactionImpl (from TransactionManagerImpl)
        PoolingSupport poolingSupport = new PartitionedPool(config.maxPoolSize, config.minPoolSize,
                config.blockingTimeoutMillis, config.idleTimeoutMinutes, config.matchOne, config.matchAll,
                config.selectOneNoMatch, config.partitionByConnectionRequestInfo, config.partitionBySubject);
        final Subject subject = new Subject();
        SubjectSource subjectSource = new SubjectSource() {
            @Override
            public Subject getSubject() {
                return subject;
            }
        };
        ConnectionTrackingCoordinator connectionTracker = new ConnectionTrackingCoordinator();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // NuxeoContainer.class.getClassLoader();

        return new GenericConnectionManager(transactionSupport, poolingSupport, subjectSource, connectionTracker,
                transactionManager, config.name, classLoader);
    }

    public static class TransactionManagerConfiguration {
        public int transactionTimeoutSeconds = 600;

        public void setTransactionTimeoutSeconds(int transactionTimeoutSeconds) {
            this.transactionTimeoutSeconds = transactionTimeoutSeconds;
        }
    }

    /**
     * Wraps a transaction manager for providing a dummy recoverable interface.
     *
     * @author matic
     *
     */
    public static class TransactionManagerWrapper implements RecoverableTransactionManager {

        protected TransactionManager tm;

        public TransactionManagerWrapper(TransactionManager tm) {
            this.tm = tm;
        }

        @Override
        public Transaction suspend() throws SystemException {
            return tm.suspend();
        }

        @Override
        public void setTransactionTimeout(int seconds) throws SystemException {
            tm.setTransactionTimeout(seconds);
        }

        @Override
        public void setRollbackOnly() throws IllegalStateException, SystemException {
            tm.setRollbackOnly();
        }

        @Override
        public void rollback() throws IllegalStateException, SecurityException, SystemException {
            tm.rollback();
        }

        @Override
        public void resume(Transaction tobj)
                throws IllegalStateException, InvalidTransactionException, SystemException {
            tm.resume(tobj);
        }

        @Override
        public Transaction getTransaction() throws SystemException {
            return tm.getTransaction();
        }

        @Override
        public int getStatus() throws SystemException {
            return tm.getStatus();
        }

        @Override
        public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException,
                RollbackException, SecurityException, SystemException {
            tm.commit();
        }

        @Override
        public void begin() throws SystemException {
            try {
                tm.begin();
            } catch (javax.transaction.NotSupportedException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void recoveryError(Exception e) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void registerNamedXAResourceFactory(NamedXAResourceFactory factory) {
            if (RecoverableTransactionManager.class.isAssignableFrom(tm.getClass())) {
                ((RecoverableTransactionManager) tm).registerNamedXAResourceFactory(factory);
            }
        }

        @Override
        public void unregisterNamedXAResourceFactory(String factory) {
            if (RecoverableTransactionManager.class.isAssignableFrom(tm.getClass())) {
                ((RecoverableTransactionManager) tm).unregisterNamedXAResourceFactory(factory);
            }
        }

    }

    /**
     * Wraps a Geronimo ConnectionManager and adds a {@link #reset} method to
     * flush the pool.
     */
    public static class ConnectionManagerWrapper implements ConnectionManager {
        private static final long serialVersionUID = 1L;

        protected AbstractConnectionManager cm;

        protected final ConnectionManagerConfiguration config;

        public ConnectionManagerWrapper(AbstractConnectionManager cm, ConnectionManagerConfiguration config) {
            this.cm = cm;
            this.config = config;
        }

        @Override
        public Object allocateConnection(ManagedConnectionFactory managedConnectionFactory,
                ConnectionRequestInfo connectionRequestInfo) throws ResourceException {
            return cm.allocateConnection(managedConnectionFactory, connectionRequestInfo);
        }

        public void reset() throws Exception {
            cm.doStop();
            cm = createConnectionManager(config);
        }
    }

    public static class ConnectionManagerConfiguration {

        public String name = "NuxeoConnectionManager";

        // transaction

        public boolean useTransactionCaching = true;

        public boolean useThreadCaching = true;

        // pool

        public boolean matchOne = true; // unused by Geronimo?

        public boolean matchAll = true;

        public boolean selectOneNoMatch = false;

        public boolean partitionByConnectionRequestInfo = false;

        public boolean partitionBySubject = true;

        public int maxPoolSize = 20;

        public int minPoolSize = 0;

        public int blockingTimeoutMillis = 100;

        public int idleTimeoutMinutes = 0; // no timeout

        public void setName(String name) {
            this.name = name;
        }

        public void setUseTransactionCaching(boolean useTransactionCaching) {
            this.useTransactionCaching = useTransactionCaching;
        }

        public void setUseThreadCaching(boolean useThreadCaching) {
            this.useThreadCaching = useThreadCaching;
        }

        public void setMatchOne(boolean matchOne) {
            this.matchOne = matchOne;
        }

        public void setMatchAll(boolean matchAll) {
            this.matchAll = matchAll;
        }

        public void setSelectOneNoMatch(boolean selectOneNoMatch) {
            this.selectOneNoMatch = selectOneNoMatch;
        }

        public void setPartitionByConnectionRequestInfo(boolean partitionByConnectionRequestInfo) {
            this.partitionByConnectionRequestInfo = partitionByConnectionRequestInfo;
        }

        public void setPartitionBySubject(boolean partitionBySubject) {
            this.partitionBySubject = partitionBySubject;
        }

        public void setMaxPoolSize(int maxPoolSize) {
            this.maxPoolSize = maxPoolSize;
        }

        public void setMinPoolSize(int minPoolSize) {
            this.minPoolSize = minPoolSize;
        }

        public void setBlockingTimeoutMillis(int blockingTimeoutMillis) {
            this.blockingTimeoutMillis = blockingTimeoutMillis;
        }

        public void setIdleTimeoutMinutes(int idleTimeoutMinutes) {
            this.idleTimeoutMinutes = idleTimeoutMinutes;
        }

    }

}