com.flexive.shared.EJBLookup.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.shared.EJBLookup.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.shared;

import com.flexive.shared.exceptions.FxLookupException;
import com.flexive.shared.interfaces.*;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.ejb.SessionContext;
import javax.naming.*;
import javax.transaction.TransactionManager;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Utility class for EJB lookups.
 *
 * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 */
public class EJBLookup {
    /**
     * EJB 3.1+ embedded application name. This must be the application name set in the container
     * and will be used for building the JNDI paths of our EJBs. The resulting path will look like this:
     *
     * <p>{@code java:global/flexive-embedded/ScriptingEngine!com.flexive.shared.interfaces.ScriptingEngine}</p>
     *
     * @since 3.1.4
     */
    public static final String EJB31_EMBEDDED_APPNAME = "flexive-embedded";

    private static final Log LOG = LogFactory.getLog(EJBLookup.class);
    private static String APPNAME = "flexive";

    private enum STRATEGY {
        APP_SIMPLENAME_LOCAL, EJB31_APP_MODULE, EJB31_MODULE, EJB31_EMBEDDED, JAVA_COMP_ENV, APP_SIMPLENAME_REMOTE, COMPLEXNAME, SIMPLENAME, SIMPLENAME_LOCAL, SIMPLENAME_REMOTE, GERONIMO_LOCAL, GERONIMO_REMOTE, MAPPED_NAME_REMOTE, UNKNOWN
    }

    private static STRATEGY used_strategy = null;
    private static final ConcurrentMap<String, Object> ejbCache = new ConcurrentHashMap<String, Object>(100);

    /**
     * Possible JNDI paths for the transaction manager
     */
    private static final List<String> TM_JNDI_PATHS = ImmutableList.of("java:TransactionManager",
            "java:comp/TransactionManager", "java:appserver/TransactionManager", "java:jboss/TransactionManager" // JBoss 7
    );

    /**
     * Protected default constructor to avoid instantiation.
     */
    protected EJBLookup() {
    }

    /**
     * Lookup of the AccountEngine EJB.
     *
     * @return a reference to the AccountEngine EJB
     */
    public static AccountEngine getAccountEngine() {
        return getEngine(AccountEngine.class);
    }

    /**
     * Lookup of the SqlSearch EJB.
     *
     * @return a reference to the SqlSearch EJB
     */
    public static SearchEngine getSearchEngine() {
        return getEngine(SearchEngine.class);
    }

    /**
     * Lookup of the Briefcase EJB.
     *
     * @return a reference to the Briefcase EJB
     */
    public static BriefcaseEngine getBriefcaseEngine() {
        return getEngine(BriefcaseEngine.class);
    }

    /**
     * Lookup of the GroupEngine EJB.
     *
     * @return a reference to the GroupEngine EJB
     */
    public static UserGroupEngine getUserGroupEngine() {
        return getEngine(UserGroupEngine.class);
    }

    /**
     * Lookup of the MandatorEngine EJB.
     *
     * @return a reference to the MandatorEngine EJB
     */
    public static MandatorEngine getMandatorEngine() {
        return getEngine(MandatorEngine.class);
    }

    /**
     * Lookup of the ACLEngine EJB.
     *
     * @return a reference to the ACLEngine EJB
     */
    public static ACLEngine getAclEngine() {
        return getEngine(ACLEngine.class);
    }

    /**
     * Lookup of the Language EJB.
     *
     * @return a reference to the Language EJB
     */
    public static LanguageEngine getLanguageEngine() {
        return getEngine(LanguageEngine.class);
    }

    /**
     * Lookup of the UserConfigurationEngine EJB.
     *
     * @return a reference to the UserConfigurationEngine EJB
     */
    public static UserConfigurationEngine getUserConfigurationEngine() {
        return getEngine(UserConfigurationEngine.class);
    }

    /**
     * Lookup of the DivisionConfigurationEngine EJB.
     *
     * @return a reference to the DivisionConfigurationEngine EJB
     */
    public static DivisionConfigurationEngine getDivisionConfigurationEngine() {
        return getEngine(DivisionConfigurationEngine.class);
    }

    /**
     * Lookup of the ApplicationConfigurationEngine EJB.
     *
     * @return a reference to the ApplicationConfigurationEngine EJB
     * @since 3.1
     */
    public static ApplicationConfigurationEngine getApplicationConfigurationEngine() {
        return getEngine(ApplicationConfigurationEngine.class);
    }

    /**
     * Lookup of the NodeConfigurationEngine EJB.
     *
     * @return a reference to the NodeConfigurationEngine EJB
     * @since 3.1
     */
    public static NodeConfigurationEngine getNodeConfigurationEngine() {
        return getEngine(NodeConfigurationEngine.class);
    }

    /**
     * Lookup of the MandatorConfigurationEngine EJB.
     * 
     * @return  a reference to the MandatorConfigurationEngine EJB
     * @since   3.1.6
     */
    public static MandatorConfigurationEngine getMandatorConfigurationEngine() {
        return getEngine(MandatorConfigurationEngine.class);
    }

    /**
     * Lookup of the GlobalConfigurationBean EJB.
     *
     * @return a reference to the GlobalConfigurationBean EJB.
     */
    public static GlobalConfigurationEngine getGlobalConfigurationEngine() {
        return getEngine(GlobalConfigurationEngine.class);
    }

    /**
     * Lookup of the FxConfiguration EJB.
     *
     * @return a reference to the FxConfiguration EJB.
     */
    public static ConfigurationEngine getConfigurationEngine() {
        return getEngine(ConfigurationEngine.class);
    }

    /**
     * Lookup of the AssignmentEngine EJB.
     *
     * @return a reference to the AssignmentEngine EJB.
     */
    public static AssignmentEngine getAssignmentEngine() {
        return getEngine(AssignmentEngine.class);
    }

    /**
     * Lookup of the TypeEngine EJB.
     *
     * @return a reference to the TypeEngine EJB.
     */
    public static TypeEngine getTypeEngine() {
        return getEngine(TypeEngine.class);
    }

    /**
     * Lookup of the SelectListEngine EJB.
     *
     * @return a reference to the SelectListEngine EJB
     */
    public static SelectListEngine getSelectListEngine() {
        return getEngine(SelectListEngine.class);
    }

    /**
     * Lookup of the WorkflowEngine EJB.
     *
     * @return a reference to the WorkflowEngine EJB.
     */
    public static WorkflowEngine getWorkflowEngine() {
        return getEngine(WorkflowEngine.class);
    }

    /**
     * Lookup of the StepEngine EJB.
     *
     * @return a reference to the StepEngine EJB.
     */
    public static StepEngine getWorkflowStepEngine() {
        return getEngine(StepEngine.class);
    }

    /**
     * Lookup of the RouteEngine EJB.
     *
     * @return a reference to the RouteEngine EJB.
     */
    public static RouteEngine getWorkflowRouteEngine() {
        return getEngine(RouteEngine.class);
    }

    /**
     * Lookup of the StepDefinitionEngine EJB.
     *
     * @return a reference to the StepDefinitionEngine EJB.
     */
    public static StepDefinitionEngine getWorkflowStepDefinitionEngine() {
        return getEngine(StepDefinitionEngine.class);
    }

    /**
     * Lookup of the ContentEngine EJB.
     *
     * @return a reference to the ContentEngine EJB.
     */
    public static ContentEngine getContentEngine() {
        return getEngine(ContentEngine.class);
    }

    /**
     * Lookup of the StatelessTest EJB.
     *
     * @return a reference to the StatelessTest EJB.
     */
    public static StatelessTest getStatelessTestInterface() {
        return getEngine(StatelessTest.class);
    }

    /**
     * Lookup of the SequencerEngine EJB.
     *
     * @return a reference to the SequencerEngine EJB.
     */
    public static SequencerEngine getSequencerEngine() {
        return getEngine(SequencerEngine.class);
    }

    /**
     * Lookup of the ScriptingEngine EJB
     *
     * @return a reference to the ScriptingEngine EJB
     */
    public static ScriptingEngine getScriptingEngine() {
        return getEngine(ScriptingEngine.class);
    }

    /**
     * Lookup of the FxTimerService EJB
     *
     * @return a reference to the FxTimerService EJB
     */
    public static FxTimerService getTimerService() {
        return getEngine(FxTimerService.class);
    }

    /**
     * Lookup of the FxTree EJB
     *
     * @return a reference to the FxTree EJB
     */
    public static TreeEngine getTreeEngine() {
        return getEngine(TreeEngine.class);
    }

    /**
     * Lookup of the ResultPreferencesEngine EJB.
     *
     * @return a reference to the ResultPreferencesEngine EJB.
     */
    public static ResultPreferencesEngine getResultPreferencesEngine() {
        return getEngine(ResultPreferencesEngine.class);
    }

    /**
     * Lookup of the HistoryTrackerEngine EJB.
     *
     * @return a reference to the HistoryTrackerEngine EJB
     */
    public static HistoryTrackerEngine getHistoryTrackerEngine() {
        return getEngine(HistoryTrackerEngine.class);
    }

    /**
     * Lookup of the CmisSearchEngine EJB.
     *
     * @return  a reference to the CmisSearchEngine EJB.
     * @since 3.1
     */
    public static CmisSearchEngine getCmisSearchEngine() {
        return getEngine(CmisSearchEngine.class);
    }

    /**
     * Lookup of the PhraseEngine EJB.
     *
     * @return a reference to the PhraseEngine EJB
     * @since 3.2.0
     */
    public static PhraseEngine getPhraseEngine() {
        return getEngine(PhraseEngine.class);
    }

    /**
     * Get a reference of the transaction manager
     *
     * @return TransactionManager
     */
    public static TransactionManager getTransactionManager() {
        for (String path : TM_JNDI_PATHS) {
            try {
                InitialContext ctx = new InitialContext();
                return (TransactionManager) ctx.lookup(path);
            } catch (NamingException e) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("No transaction manager found under JNDI path " + path);
                }
            }
        }
        throw new FxLookupException("Failed to lookup transaction manager").asRuntimeException();
    }

    /**
     * Get a reference of the current EJB session context.
     *
     * @return the EJB session context
     */
    public static SessionContext getSessionContext() {
        try {
            final InitialContext ctx = new InitialContext();
            return (SessionContext) ctx.lookup("java:comp/EJBContext");
        } catch (NamingException e) {
            throw new FxLookupException("Failed to lookup session context: " + e.getMessage(), e)
                    .asRuntimeException();
        }
    }

    /**
     * Lookup the EJB found under the given Class's name. Uses default flexive naming scheme.
     *
     * @param type        EJB interface class instance
     * @param appName     EJB application name
     * @param environment optional environment for creating the initial context
     * @param <T>         EJB interface type
     * @return a reference to the given EJB
     */
    protected static <T> T getInterface(Class<T> type, String appName, Hashtable<String, String> environment) {
        // Try to obtain interface from the lookup cache
        Object ointerface = ejbCache.get(type.getName());
        if (ointerface != null) {
            return type.cast(ointerface);
        }

        // Cache miss: obtain interface and store it in the cache
        Hashtable<String, String> env;
        synchronized (EJBLookup.class) {
            String name;
            InitialContext ctx = null;
            env = new Hashtable<String, String>(10);
            try {
                if (environment != null)
                    env.putAll(environment);
                if (used_strategy == null) {
                    appName = discoverStrategy(appName, env, type);
                    if (used_strategy != null) {
                        LOG.info("Working lookup strategy: " + used_strategy);
                    } else {
                        LOG.error("No working lookup strategy found! Possibly because of pending redeployment.");
                    }
                }
                prepareEnvironment(used_strategy, env);
                ctx = new InitialContext(env);
                name = buildName(appName, type);
                ointerface = ctx.lookup(name);
                ejbCache.putIfAbsent(type.getName(), ointerface);

                return type.cast(ointerface);
            } catch (Exception exc) {
                //try one more time with a strategy rediscovery for that bean
                //this can happen if some beans use mapped names and some not
                used_strategy = null;
                try {
                    env.clear();
                    if (environment != null)
                        env.putAll(environment);
                    appName = discoverStrategy(appName, env, type);
                    if (used_strategy != null) {
                        prepareEnvironment(used_strategy, env);
                        ctx = new InitialContext(env);
                        name = buildName(appName, type);
                        ointerface = ctx.lookup(name);
                        ejbCache.putIfAbsent(type.getName(), ointerface);
                        return type.cast(ointerface);
                    }
                } catch (Exception e) {
                    LOG.warn("Attempt to rediscover lookup strategy for " + type + " failed!", e);
                }
                throw new FxLookupException(LOG, exc, "ex.ejb.lookup.failure", type, exc).asRuntimeException();
            } finally {
                if (ctx != null)
                    try {
                        ctx.close();
                    } catch (NamingException e) {
                        LOG.error("Failed to close context: " + e.getMessage(), e);
                    }
            }
        }
    }

    /**
     * Discover which lookup strategy works for the given class
     *
     * @param appName     EJB application name
     * @param environment properties passed to the initial context
     * @param type        the class
     * @return appName (may have changed)
     */
    private static <T> String discoverStrategy(String appName, final Hashtable<String, String> environment,
            Class<T> type) {
        InitialContext ctx = null;
        for (STRATEGY strat : STRATEGY.values()) {
            if (strat == STRATEGY.UNKNOWN)
                continue;
            used_strategy = strat;
            try {
                final Hashtable<String, String> env = environment != null
                        ? new Hashtable<String, String>(environment)
                        : new Hashtable<String, String>();
                prepareEnvironment(strat, env);
                ctx = new InitialContext(env);
                ctx.lookup(buildName(appName, type));

                if (used_strategy == STRATEGY.EJB31_MODULE) {
                    // we need to resolve all interfaces required by non-web components (stream server, scheduler),
                    // since they run outside the module context

                    resolveKnownInterfaces();
                }
                return appName;
            } catch (Exception e) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Strategy " + strat + " failed: " + e.getMessage(), e);
                }
                //ignore and try next
            }
        }
        //houston, we have a problem - try locale and remote with appname again iterating through all "root" ctx bindings
        //this can happen if the ear is not named flexive.ear
        try {
            if (ctx == null)
                ctx = new InitialContext(environment);
            NamingEnumeration<NameClassPair> ncpe = ctx.list("");
            while (ncpe.hasMore()) {
                NameClassPair ncp = ncpe.next();
                if (ncp.getClassName().endsWith("NamingContext")) {
                    appName = ncp.getName();
                    try {
                        used_strategy = STRATEGY.APP_SIMPLENAME_LOCAL;
                        ctx.lookup(buildName(ncp.getName(), type));
                        APPNAME = ncp.getName();
                        LOG.info("Using application name [" + appName + "] for lookups!");
                        return APPNAME;
                    } catch (Exception e) {
                        //ignore and try remote
                    }
                    try {
                        used_strategy = STRATEGY.APP_SIMPLENAME_REMOTE;
                        ctx.lookup(buildName(ncp.getName(), type));
                        APPNAME = ncp.getName();
                        LOG.info("Using application name [" + appName + "] for lookups!");
                        return APPNAME;
                    } catch (Exception e) {
                        //ignore and try remote
                    }
                }
            }
        } catch (Exception e) {
            LOG.warn(e);
        }
        used_strategy = null;
        return appName;
    }

    /**
     * Adjust the environment with strategy/application server specific settings
     *
     * @param strat       strategy
     * @param environment environment
     */
    private static void prepareEnvironment(STRATEGY strat, Hashtable<String, String> environment) {
        if (strat == STRATEGY.GERONIMO_LOCAL || strat == STRATEGY.GERONIMO_REMOTE) {
            environment.put("java.naming.factory.initial",
                    strat == STRATEGY.GERONIMO_LOCAL ? "org.apache.openejb.client.LocalInitialContextFactory"
                            : "org.apache.openejb.client.RemoteInitialContextFactory");
            if (!StringUtils.isEmpty(System.getProperty("java.naming.provider.url")))
                environment.put("java.naming.provider.url", System.getProperty("java.naming.provider.url"));
            else
                environment.put("java.naming.provider.url", "localhost:4201");
        }
    }

    /**
     * Adjust the environment with strategy/application server specific settings
     *
     * @return Context
     * @throws NamingException on errors
     */
    public static Context getInitialContext() throws NamingException {
        if (used_strategy == STRATEGY.GERONIMO_LOCAL || used_strategy == STRATEGY.GERONIMO_REMOTE) {
            return getOpenEJBRootContext();

        } else
            return new InitialContext();
    }

    private static Context getOpenEJBRootContext() {
        // use reflection to return OpenEJB's system context to enable access to the configured datasource
        // in every bean - the standard OpenEJB context leads to quirky workarounds for the datasources
        try {
            final Class<?> clsSystem = Class.forName("org.apache.openejb.loader.SystemInstance");

            // invoke static method SystemInstance.get()
            final Object systemInstance = clsSystem.getMethod("get").invoke(null);

            // invoke getComponent on systemInstance
            final Class<?> clsContainer = Class.forName("org.apache.openejb.spi.ContainerSystem");
            final Object containerSystem = clsSystem.getMethod("getComponent", Class.class).invoke(systemInstance,
                    clsContainer);

            // return system JNDI context - containerSystem.getJNDIContext()
            return (Context) clsContainer.getMethod("getJNDIContext").invoke(containerSystem);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(
                    "Lookup strategy is OpenEJB, but OpenEJB classes missing: " + e.getMessage(), e);
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    /**
     * Build the correct JNDI name to request for lookups depending on the discovered lookup strategy
     *
     * @param appName EJB application name
     * @param type    the class to lookup
     * @return JNDI name
     */
    private static <T> String buildName(String appName, Class<T> type) {
        switch (used_strategy) {
        case APP_SIMPLENAME_LOCAL:
            return appName + "/" + type.getSimpleName() + "/local";
        case APP_SIMPLENAME_REMOTE:
            return appName + "/" + type.getSimpleName() + "/remote";
        case COMPLEXNAME:
            return type.getCanonicalName();
        case SIMPLENAME:
            return type.getSimpleName();
        case SIMPLENAME_LOCAL:
            return type.getSimpleName() + "/local";
        case SIMPLENAME_REMOTE:
            return type.getSimpleName() + "/remote";
        case JAVA_COMP_ENV:
            return "java:comp/env/" + type.getSimpleName();
        case GERONIMO_LOCAL:
            return "/" + type.getSimpleName() + "Local";
        case GERONIMO_REMOTE:
            return "/" + type.getSimpleName() + "Remote";
        case MAPPED_NAME_REMOTE:
            return type.getSimpleName() + "#" + type.getCanonicalName();
        case EJB31_MODULE:
            // EJB 3.1: EJB packaged in module
            return "java:module/" + type.getSimpleName() + "!" + type.getCanonicalName() + "Local";
        case EJB31_APP_MODULE:
            // EJB 3.1: flexive-ejb module in EAR
            return "java:app/flexive-ejb/" + type.getSimpleName() + "!" + type.getCanonicalName() + "Local";
        case EJB31_EMBEDDED:
            // EJB 3.1: embedded container with global paths
            return "java:global/" + EJB31_EMBEDDED_APPNAME + "/" + type.getSimpleName() + "!"
                    + type.getCanonicalName() + "Local";
        default:
            throw new FxLookupException("Unsupported/unknown lookup strategy " + used_strategy + "!")
                    .asRuntimeException();
        }
    }

    /**
     * Lookup the EJB found under the given Class's name. Uses default flexive naming scheme.
     *
     * @param appName EJB application name
     * @param type    EJB interface class instance
     * @param <T>     EJB interface type
     * @return a reference to the given EJB
     */
    public static <T> T getEngine(String appName, Class<T> type) {
        return getInterface(type, appName, null);
    }

    /**
     * Lookup the EJB found under the given Class's name. Uses default flexive naming scheme.
     *
     * @param type EJB interface class instance
     * @param <T>  EJB interface type
     * @return a reference to the given EJB
     */
    public static <T> T getEngine(Class<T> type) {
        return getInterface(type, APPNAME, null);
    }

    private static void resolveKnownInterfaces() throws NamingException {
        // FIXME: this is a hack since it does not seem to be possible to browse the java:module path
        // to enumerate all registered local interfaces. Of course this breaks for user-defined EJBs
        // or if a new EJB is not registered here.

        getAccountEngine();
        getAclEngine();
        getApplicationConfigurationEngine();
        getAssignmentEngine();
        getBriefcaseEngine();
        getCmisSearchEngine();
        getConfigurationEngine();
        getContentEngine();
        getDivisionConfigurationEngine();
        getGlobalConfigurationEngine();
        getHistoryTrackerEngine();
        getLanguageEngine();
        getMandatorEngine();
        getNodeConfigurationEngine();
        getResultPreferencesEngine();
        getScriptingEngine();
        getSearchEngine();
        getSelectListEngine();
        getSequencerEngine();
        getTimerService();
        getTransactionManager();
        getTreeEngine();
        getTypeEngine();
        getUserConfigurationEngine();
        getUserGroupEngine();
        getWorkflowEngine();
        getWorkflowRouteEngine();
        getWorkflowStepDefinitionEngine();
        getWorkflowStepEngine();
    }

    /**
     * Clear the static interface cache on shutdown.
     *
     * @since 3.1.4
     */
    public static synchronized void clearCache() {
        ejbCache.clear();
    }
}