All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.openejb.assembler.classic.Assembler Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.openejb.assembler.classic;

import org.apache.geronimo.connector.GeronimoBootstrapContext;
import org.apache.geronimo.connector.outbound.AbstractConnectionManager;
import org.apache.geronimo.connector.work.GeronimoWorkManager;
import org.apache.geronimo.connector.work.HintsContextHandler;
import org.apache.geronimo.connector.work.TransactionContextHandler;
import org.apache.geronimo.connector.work.WorkContextHandler;
import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
import org.apache.openejb.AppContext;
import org.apache.openejb.BeanContext;
import org.apache.openejb.BeanType;
import org.apache.openejb.ClassLoaderUtil;
import org.apache.openejb.Container;
import org.apache.openejb.DeploymentContext;
import org.apache.openejb.DuplicateDeploymentIdException;
import org.apache.openejb.Extensions;
import org.apache.openejb.Injection;
import org.apache.openejb.JndiConstants;
import org.apache.openejb.MethodContext;
import org.apache.openejb.NoSuchApplicationException;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.UndeployException;
import org.apache.openejb.api.DestroyableResource;
import org.apache.openejb.assembler.classic.event.AssemblerAfterApplicationCreated;
import org.apache.openejb.assembler.classic.event.AssemblerBeforeApplicationDestroyed;
import org.apache.openejb.assembler.classic.event.AssemblerCreated;
import org.apache.openejb.assembler.classic.event.AssemblerDestroyed;
import org.apache.openejb.assembler.classic.event.BeforeStartEjbs;
import org.apache.openejb.assembler.classic.event.ContainerSystemPostCreate;
import org.apache.openejb.assembler.classic.event.ContainerSystemPreDestroy;
import org.apache.openejb.assembler.monitoring.JMXContainer;
import org.apache.openejb.async.AsynchronousPool;
import org.apache.openejb.cdi.CdiAppContextsService;
import org.apache.openejb.cdi.CdiBuilder;
import org.apache.openejb.cdi.CdiResourceInjectionService;
import org.apache.openejb.cdi.CdiScanner;
import org.apache.openejb.cdi.CustomELAdapter;
import org.apache.openejb.cdi.ManagedSecurityService;
import org.apache.openejb.cdi.OpenEJBJndiService;
import org.apache.openejb.cdi.OpenEJBTransactionService;
import org.apache.openejb.cdi.OptimizedLoaderService;
import org.apache.openejb.classloader.ClassLoaderConfigurer;
import org.apache.openejb.classloader.CompositeClassLoaderConfigurer;
import org.apache.openejb.component.ClassLoaderEnricher;
import org.apache.openejb.config.ConfigurationFactory;
import org.apache.openejb.config.NewLoaderLogic;
import org.apache.openejb.config.QuickJarsTxtParser;
import org.apache.openejb.config.TldScanner;
import org.apache.openejb.core.ConnectorReference;
import org.apache.openejb.core.CoreContainerSystem;
import org.apache.openejb.core.CoreUserTransaction;
import org.apache.openejb.core.JndiFactory;
import org.apache.openejb.core.ParentClassLoaderFinder;
import org.apache.openejb.core.ServerFederation;
import org.apache.openejb.core.SimpleTransactionSynchronizationRegistry;
import org.apache.openejb.core.TransactionSynchronizationRegistryWrapper;
import org.apache.openejb.core.WebContext;
import org.apache.openejb.core.ivm.IntraVmProxy;
import org.apache.openejb.core.ivm.naming.ContextualJndiReference;
import org.apache.openejb.core.ivm.naming.IvmContext;
import org.apache.openejb.core.ivm.naming.IvmJndiFactory;
import org.apache.openejb.core.ivm.naming.LazyObjectReference;
import org.apache.openejb.core.ivm.naming.Reference;
import org.apache.openejb.core.security.SecurityContextHandler;
import org.apache.openejb.core.timer.EjbTimerServiceImpl;
import org.apache.openejb.core.timer.MemoryTimerStore;
import org.apache.openejb.core.timer.NullEjbTimerServiceImpl;
import org.apache.openejb.core.timer.ScheduleData;
import org.apache.openejb.core.timer.TimerStore;
import org.apache.openejb.core.transaction.JtaTransactionPolicyFactory;
import org.apache.openejb.core.transaction.SimpleBootstrapContext;
import org.apache.openejb.core.transaction.SimpleWorkManager;
import org.apache.openejb.core.transaction.TransactionPolicyFactory;
import org.apache.openejb.core.transaction.TransactionType;
import org.apache.openejb.javaagent.Agent;
import org.apache.openejb.jpa.integration.MakeTxLookup;
import org.apache.openejb.loader.IO;
import org.apache.openejb.loader.JarLocation;
import org.apache.openejb.loader.Options;
import org.apache.openejb.loader.ProvisioningUtil;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.monitoring.DynamicMBeanWrapper;
import org.apache.openejb.monitoring.LocalMBeanServer;
import org.apache.openejb.monitoring.ObjectNameBuilder;
import org.apache.openejb.monitoring.remote.RemoteResourceMonitor;
import org.apache.openejb.observer.Observes;
import org.apache.openejb.persistence.JtaEntityManagerRegistry;
import org.apache.openejb.persistence.PersistenceClassLoaderHandler;
import org.apache.openejb.quartz.Scheduler;
import org.apache.openejb.resource.GeronimoConnectionManagerFactory;
import org.apache.openejb.resource.PropertiesFactory;
import org.apache.openejb.resource.jdbc.DataSourceFactory;
import org.apache.openejb.spi.ApplicationServer;
import org.apache.openejb.spi.ContainerSystem;
import org.apache.openejb.spi.SecurityService;
import org.apache.openejb.util.Contexts;
import org.apache.openejb.util.DaemonThreadFactory;
import org.apache.openejb.util.ExecutorBuilder;
import org.apache.openejb.util.Join;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.Messages;
import org.apache.openejb.util.OpenEJBErrorHandler;
import org.apache.openejb.util.PropertiesHelper;
import org.apache.openejb.util.PropertyPlaceHolderHelper;
import org.apache.openejb.util.References;
import org.apache.openejb.util.SafeToolkit;
import org.apache.openejb.util.SetAccessible;
import org.apache.openejb.util.SuperProperties;
import org.apache.openejb.util.URISupport;
import org.apache.openejb.util.URLs;
import org.apache.openejb.util.classloader.ClassLoaderAwareHandler;
import org.apache.openejb.util.classloader.URLClassLoaderFirst;
import org.apache.openejb.util.proxy.ProxyFactory;
import org.apache.openejb.util.proxy.ProxyManager;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.container.BeanManagerImpl;
import org.apache.webbeans.inject.OWBInjector;
import org.apache.webbeans.logger.JULLoggerFactory;
import org.apache.webbeans.spi.ContainerLifecycle;
import org.apache.webbeans.spi.ContextsService;
import org.apache.webbeans.spi.JNDIService;
import org.apache.webbeans.spi.LoaderService;
import org.apache.webbeans.spi.ResourceInjectionService;
import org.apache.webbeans.spi.ScannerService;
import org.apache.webbeans.spi.TransactionService;
import org.apache.webbeans.spi.adaptor.ELAdaptor;
import org.apache.xbean.finder.AnnotationFinder;
import org.apache.xbean.finder.ClassLoaders;
import org.apache.xbean.finder.ResourceFinder;
import org.apache.xbean.finder.UrlSet;
import org.apache.xbean.finder.archive.ClassesArchive;
import org.apache.xbean.recipe.ConstructionException;
import org.apache.xbean.recipe.ObjectRecipe;
import org.apache.xbean.recipe.Option;
import org.apache.xbean.recipe.UnsetPropertiesRecipe;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.resource.cci.Connection;
import javax.resource.cci.ConnectionFactory;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ManagedConnectionFactory;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.ResourceAdapterInternalException;
import javax.resource.spi.XATerminator;
import javax.resource.spi.work.WorkManager;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.io.ByteArrayInputStream;
import java.io.Externalizable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

import static org.apache.openejb.util.Classes.ancestors;

@SuppressWarnings({"UnusedDeclaration", "UnqualifiedFieldAccess", "UnqualifiedMethodAccess"})
public class Assembler extends AssemblerTool implements org.apache.openejb.spi.Assembler, JndiConstants {

    /**
     * Private static
     */
    private static final AtomicReference SLF4J_RESET = new AtomicReference(null);
    private static final ReentrantLock lock = new ReentrantLock(true);
    private static final String GLOBAL_UNIQUE_ID = "global";

    /**
     * Public static
     */
    public static final String OPENEJB_URL_PKG_PREFIX = IvmContext.class.getPackage().getName();
    public static final String OPENEJB_JPA_DEPLOY_TIME_ENHANCEMENT_PROP = "openejb.jpa.deploy-time-enhancement";
    public static final String PROPAGATE_APPLICATION_EXCEPTIONS = "openejb.propagate.application-exceptions";
    public static final String TOMEE_DATASOURCE_DESTROY_TIMEOUT = "tomee.datasource.destroy.timeout.ms";
    public static final String TIMER_STORE_CLASS = "timerStore.class";
    public static final String OPENEJB_TIMERS_ON = "openejb.timers.on";
    public static final Class[] VALIDATOR_FACTORY_INTERFACES = new Class[]{ValidatorFactory.class};
    public static final Class[] VALIDATOR_INTERFACES = new Class[]{Validator.class};

    static {
        // avoid linkage error on mac
        // adding just in case others run into in their tests
        JULLoggerFactory.class.getName();

        //Cache slf4j method if available
        if (SystemInstance.get().getOptions().get("tomee.slf4j.deployment.reset", false)) {
            try {
                final Class c = Class.forName("org.slf4j.LoggerFactory");
                final Method m = c.getDeclaredMethod("reset", null);
                m.setAccessible(true);
                Assembler.SLF4J_RESET.set(m);
            } catch (final ClassNotFoundException e) {
                System.out.println("Assembler.resetSlf4j: " + e.getMessage());
            } catch (final NoSuchMethodException e) {
                System.out.println("Assembler.resetSlf4j: " + e.getMessage());
            }
        }
    }

    private final Map deployedApplications = new HashMap();
    private final Map creationalContextForAppMbeans = new HashMap();
    private final Set containerObjectNames = new HashSet();
    private final RemoteResourceMonitor remoteResourceMonitor = new RemoteResourceMonitor();
    private final Messages messages = new Messages(Assembler.class.getPackage().getName());

    private final CoreContainerSystem containerSystem;
    private final PersistenceClassLoaderHandler persistenceClassLoaderHandler;
    private final JndiBuilder jndiBuilder;
    protected OpenEjbConfigurationFactory configFactory;
    private final boolean skipLoaderIfPossible;
    private TransactionManager transactionManager;
    private SecurityService securityService;

    //Used outside of this class
    private final Logger logger;

    protected SafeToolkit toolkit = SafeToolkit.getToolkit("Assembler");
    protected OpenEjbConfiguration config;

    public Assembler() {
        this(new IvmJndiFactory());
    }

    public Assembler(final JndiFactory jndiFactory) {
        logger = Logger.getInstance(LogCategory.OPENEJB_STARTUP, Assembler.class);
        skipLoaderIfPossible = "true".equalsIgnoreCase(SystemInstance.get().getProperty("openejb.classloader.skip-app-loader-if-possible", "true"));
        persistenceClassLoaderHandler = new PersistenceClassLoaderHandlerImpl();

        installNaming();

        final SystemInstance system = SystemInstance.get();

        system.setComponent(org.apache.openejb.spi.Assembler.class, this);
        system.setComponent(Assembler.class, this);

        containerSystem = new CoreContainerSystem(jndiFactory);
        system.setComponent(ContainerSystem.class, containerSystem);

        jndiBuilder = new JndiBuilder(containerSystem.getJNDIContext());

        setConfiguration(new OpenEjbConfiguration());

        final ApplicationServer appServer = system.getComponent(ApplicationServer.class);
        if (appServer == null) {
            system.setComponent(ApplicationServer.class, new ServerFederation());
        }

        system.setComponent(EjbResolver.class, new EjbResolver(null, EjbResolver.Scope.GLOBAL));

        installExtensions();

        system.fireEvent(new AssemblerCreated());
    }

    @Override
    public ContainerSystem getContainerSystem() {
        return containerSystem;
    }

    @Override
    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    @Override
    public SecurityService getSecurityService() {
        return securityService;
    }

    public void addDeploymentListener(final DeploymentListener deploymentListener) {

        final ReentrantLock l = lock;
        l.lock();

        try {
            logger.warning("DeploymentListener API is replaced by @Observes event");
            SystemInstance.get().addObserver(new DeploymentListenerObserver(deploymentListener));
        } finally {
            l.unlock();
        }
    }

    public void removeDeploymentListener(final DeploymentListener deploymentListener) {

        final ReentrantLock l = lock;
        l.lock();

        try {
            // the wrapping is done here to get the correct equals/hashcode methods
            SystemInstance.get().removeObserver(new DeploymentListenerObserver(deploymentListener));
        } finally {
            l.unlock();
        }
    }

    private void installExtensions() {
        try {
            final Collection urls = NewLoaderLogic.applyBuiltinExcludes(new UrlSet(Assembler.class.getClassLoader()).excludeJvm()).getUrls();
            Extensions.installExtensions(new Extensions.Finder("META-INF", false, urls.toArray(new URL[urls.size()])));
            return;
        } catch (final IOException e) {
            // no-op
        }

        // if an error occurred do it brutely
        Extensions.installExtensions(new Extensions.Finder("META-INF", true));
    }

    private void setConfiguration(final OpenEjbConfiguration config) {
        this.config = config;
        if (config.containerSystem == null) {
            config.containerSystem = new ContainerSystemInfo();
        }

        if (config.facilities == null) {
            config.facilities = new FacilitiesInfo();
        }

        SystemInstance.get().setComponent(OpenEjbConfiguration.class, this.config);
    }

    @Override
    public void init(final Properties props) throws OpenEJBException {
        this.props = new Properties(props);
        final Options options = new Options(props, SystemInstance.get().getOptions());
        final String className = options.get("openejb.configurator", "org.apache.openejb.config.ConfigurationFactory");

        if ("org.apache.openejb.config.ConfigurationFactory".equals(className)) {
            configFactory = new ConfigurationFactory(); // no need to use reflection
        } else {
            configFactory = (OpenEjbConfigurationFactory) toolkit.newInstance(className);
        }
        configFactory.init(props);
        SystemInstance.get().setComponent(OpenEjbConfigurationFactory.class, configFactory);
    }

    public static void installNaming() {
        if (SystemInstance.get().hasProperty("openejb.geronimo")) {
            return;
        }

        /* Add IntraVM JNDI service /////////////////////*/
        installNaming(OPENEJB_URL_PKG_PREFIX);
        /*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
    }

    public static void installNaming(final String prefix) {
        installNaming(prefix, false);
    }

    public static void installNaming(final String prefix, final boolean clean) {

        final ReentrantLock l = lock;
        l.lock();

        try {
            final Properties systemProperties = System.getProperties();

            String str = systemProperties.getProperty(Context.URL_PKG_PREFIXES);
            if (str == null || clean) {
                str = prefix;
            } else if (!str.contains(prefix)) {
                str = str + ":" + prefix;
            }
            systemProperties.setProperty(Context.URL_PKG_PREFIXES, str);
        } finally {
            l.unlock();
        }
    }

    private static final ThreadLocal> context = new ThreadLocal>();

    public static void setContext(final Map map) {
        context.set(map);
    }

    public static Map getContext() {
        Map map = context.get();
        if (map == null) {
            map = new HashMap();
            context.set(map);
        }
        return map;
    }

    @Override
    public void build() throws OpenEJBException {
        setContext(new HashMap());
        try {
            final OpenEjbConfiguration config = getOpenEjbConfiguration();
            buildContainerSystem(config);
        } catch (final OpenEJBException ae) {
            /* OpenEJBExceptions contain useful information and are debbugable.
             * Let the exception pass through to the top and be logged.
             */
            throw ae;
        } catch (final Exception e) {
            /* General Exceptions at this level are too generic and difficult to debug.
             * These exceptions are considered unknown bugs and are fatal.
             * If you get an error at this level, please trap and handle the error
             * where it is most relevant.
             */
            OpenEJBErrorHandler.handleUnknownError(e, "Assembler");
            throw new OpenEJBException(e);
        } finally {
            context.set(null);
        }
    }

    protected OpenEjbConfiguration getOpenEjbConfiguration() throws OpenEJBException {
        return configFactory.getOpenEjbConfiguration();
    }

    /////////////////////////////////////////////////////////////////////
    ////
    ////    Public Methods Used for Assembly
    ////
    /////////////////////////////////////////////////////////////////////

    /**
     * When given a complete OpenEjbConfiguration graph this method
     * will construct an entire container system and return a reference to that
     * container system, as ContainerSystem instance.
     * 

* This method leverage the other assemble and apply methods which * can be used independently. *

* Assembles and returns the {@link CoreContainerSystem} using the * information from the {@link OpenEjbConfiguration} object passed in. *

     * This method performs the following actions(in order):
     *
     * 1  Assembles ProxyFactory
     * 2  Assembles External JNDI Contexts
     * 3  Assembles TransactionService
     * 4  Assembles SecurityService
     * 5  Assembles ConnectionManagers
     * 6  Assembles Connectors
     * 7  Assembles Containers
     * 8  Assembles Applications
     * 
* * @param configInfo OpenEjbConfiguration * @throws Exception if there was a problem constructing the ContainerSystem. * @see OpenEjbConfiguration */ @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public void buildContainerSystem(final OpenEjbConfiguration configInfo) throws Exception { final SystemInstance systemInstance = SystemInstance.get(); if (systemInstance.getOptions().get(OPENEJB_JPA_DEPLOY_TIME_ENHANCEMENT_PROP, false)) { systemInstance.addObserver(new DeployTimeEnhancer()); } for (final ServiceInfo serviceInfo : configInfo.facilities.services) { createService(serviceInfo); } final ContainerSystemInfo containerSystemInfo = configInfo.containerSystem; if (configInfo.facilities.intraVmServer != null) { createProxyFactory(configInfo.facilities.intraVmServer); } for (final JndiContextInfo contextInfo : configInfo.facilities.remoteJndiContexts) { createExternalContext(contextInfo); } createTransactionManager(configInfo.facilities.transactionService); createSecurityService(configInfo.facilities.securityService); final Set reservedResourceIds = new HashSet(configInfo.facilities.resources.size()); for (final AppInfo appInfo : containerSystemInfo.applications) { reservedResourceIds.addAll(appInfo.resourceIds); } final Set rIds = new HashSet(configInfo.facilities.resources.size()); for (final ResourceInfo resourceInfo : configInfo.facilities.resources) { createResource(resourceInfo); rIds.add(resourceInfo.id); } rIds.removeAll(reservedResourceIds); final ContainerSystem containerSystem = systemInstance.getComponent(ContainerSystem.class); if (containerSystem != null) { postConstructResources(rIds, ParentClassLoaderFinder.Helper.get(), containerSystem.getJNDIContext(), null); }else { logger.error("ContainerSystem not initialized"); } // Containers for (final ContainerInfo serviceInfo : containerSystemInfo.containers) { createContainer(serviceInfo); } createJavaGlobal(); // before any deployment bind global to be able to share the same context for (final AppInfo appInfo : containerSystemInfo.applications) { try { createApplication(appInfo, createAppClassLoader(appInfo)); } catch (final DuplicateDeploymentIdException e) { // already logged. } catch (final Throwable e) { logger.error("appNotDeployed", e, appInfo.path); final DeploymentExceptionManager exceptionManager = systemInstance.getComponent(DeploymentExceptionManager.class); if (Exception.class.isInstance(exceptionManager)) { exceptionManager.saveDeploymentException(appInfo, Exception.class.cast(exceptionManager)); } } } systemInstance.fireEvent(new ContainerSystemPostCreate()); } private void createJavaGlobal() { try { containerSystem.getJNDIContext().createSubcontext("global"); } catch (final NamingException e) { // no-op } } public boolean isDeployed(final String path) { return deployedApplications.containsKey(ProvisioningUtil.realLocation(path)); } public Collection getDeployedApplications() { return new ArrayList(deployedApplications.values()); } public AppContext createApplication(final EjbJarInfo ejbJar) throws NamingException, IOException, OpenEJBException { return createEjbJar(ejbJar); } public AppContext createEjbJar(final EjbJarInfo ejbJar) throws NamingException, IOException, OpenEJBException { final AppInfo appInfo = new AppInfo(); appInfo.path = ejbJar.path; appInfo.appId = ejbJar.moduleName; appInfo.ejbJars.add(ejbJar); return createApplication(appInfo); } public AppContext createApplication(final EjbJarInfo ejbJar, final ClassLoader classLoader) throws NamingException, IOException, OpenEJBException { return createEjbJar(ejbJar, classLoader); } public AppContext createEjbJar(final EjbJarInfo ejbJar, final ClassLoader classLoader) throws NamingException, IOException, OpenEJBException { final AppInfo appInfo = new AppInfo(); appInfo.path = ejbJar.path; appInfo.appId = ejbJar.moduleName; appInfo.ejbJars.add(ejbJar); return createApplication(appInfo, classLoader); } public AppContext createClient(final ClientInfo clientInfo) throws NamingException, IOException, OpenEJBException { final AppInfo appInfo = new AppInfo(); appInfo.path = clientInfo.path; appInfo.appId = clientInfo.moduleId; appInfo.clients.add(clientInfo); return createApplication(appInfo); } public AppContext createClient(final ClientInfo clientInfo, final ClassLoader classLoader) throws NamingException, IOException, OpenEJBException { final AppInfo appInfo = new AppInfo(); appInfo.path = clientInfo.path; appInfo.appId = clientInfo.moduleId; appInfo.clients.add(clientInfo); return createApplication(appInfo, classLoader); } public AppContext createConnector(final ConnectorInfo connectorInfo) throws NamingException, IOException, OpenEJBException { final AppInfo appInfo = new AppInfo(); appInfo.path = connectorInfo.path; appInfo.appId = connectorInfo.moduleId; appInfo.connectors.add(connectorInfo); return createApplication(appInfo); } public AppContext createConnector(final ConnectorInfo connectorInfo, final ClassLoader classLoader) throws NamingException, IOException, OpenEJBException { final AppInfo appInfo = new AppInfo(); appInfo.path = connectorInfo.path; appInfo.appId = connectorInfo.moduleId; appInfo.connectors.add(connectorInfo); return createApplication(appInfo, classLoader); } public AppContext createWebApp(final WebAppInfo webAppInfo) throws NamingException, IOException, OpenEJBException { final AppInfo appInfo = new AppInfo(); appInfo.path = webAppInfo.path; appInfo.appId = webAppInfo.moduleId; appInfo.webApps.add(webAppInfo); return createApplication(appInfo); } public AppContext createWebApp(final WebAppInfo webAppInfo, final ClassLoader classLoader) throws NamingException, IOException, OpenEJBException { final AppInfo appInfo = new AppInfo(); appInfo.path = webAppInfo.path; appInfo.appId = webAppInfo.moduleId; appInfo.webApps.add(webAppInfo); return createApplication(appInfo, classLoader); } public AppContext createApplication(final AppInfo appInfo) throws OpenEJBException, IOException, NamingException { return createApplication(appInfo, createAppClassLoader(appInfo)); } public AppContext createApplication(final AppInfo appInfo, final ClassLoader classLoader) throws OpenEJBException, IOException, NamingException { return createApplication(appInfo, classLoader, true); } private AppContext createApplication(final AppInfo appInfo, ClassLoader classLoader, final boolean start) throws OpenEJBException, IOException, NamingException { try { mergeServices(appInfo); } catch (final URISyntaxException e) { logger.info("Failed to merge resources.xml services and appInfo.properties"); } // The path is used in the UrlCache, command line deployer, JNDI name templates, tomcat integration and a few other places if (appInfo.appId == null) { throw new IllegalArgumentException("AppInfo.appId cannot be null"); } if (appInfo.path == null) { appInfo.path = appInfo.appId; } Extensions.addExtensions(classLoader, appInfo.eventClassesNeedingAppClassloader); logger.info("createApplication.start", appInfo.path); final Context containerSystemContext = containerSystem.getJNDIContext(); // To start out, ensure we don't already have any beans deployed with duplicate IDs. // This is a conflict we fail to handle. this.checkForDuplicates(appInfo); //Construct the global and app jndi contexts for this app final InjectionBuilder injectionBuilder = new InjectionBuilder(classLoader); final Set injections = new HashSet(); injections.addAll(injectionBuilder.buildInjections(appInfo.globalJndiEnc)); injections.addAll(injectionBuilder.buildInjections(appInfo.appJndiEnc)); final JndiEncBuilder globalBuilder = new JndiEncBuilder(appInfo.globalJndiEnc, injections, appInfo.appId, null, GLOBAL_UNIQUE_ID, classLoader, appInfo.properties); final Map globalBindings = globalBuilder.buildBindings(JndiEncBuilder.JndiScope.global); final Context globalJndiContext = globalBuilder.build(globalBindings); final JndiEncBuilder appBuilder = new JndiEncBuilder(appInfo.appJndiEnc, injections, appInfo.appId, null, appInfo.appId, classLoader, appInfo.properties); final Map appBindings = appBuilder.buildBindings(JndiEncBuilder.JndiScope.app); final Context appJndiContext = appBuilder.build(appBindings); try { // Generate the cmp2/cmp1 concrete subclasses final CmpJarBuilder cmpJarBuilder = new CmpJarBuilder(appInfo, classLoader); final File generatedJar = cmpJarBuilder.getJarFile(); if (generatedJar != null) { classLoader = ClassLoaderUtil.createClassLoader(appInfo.path, new URL[]{generatedJar.toURI().toURL()}, classLoader); } final AppContext appContext = new AppContext(appInfo.appId, SystemInstance.get(), classLoader, globalJndiContext, appJndiContext, appInfo.standaloneModule); appContext.getProperties().putAll(appInfo.properties); appContext.getInjections().addAll(injections); appContext.getBindings().putAll(globalBindings); appContext.getBindings().putAll(appBindings); containerSystem.addAppContext(appContext); appContext.set(AsynchronousPool.class, AsynchronousPool.create(appContext)); final Map lazyValidatorFactories = new HashMap(); final Map lazyValidators = new HashMap(); final boolean isGeronimo = SystemInstance.get().hasProperty("openejb.geronimo"); // Load units and fill validator maps this.loadPersistenceUnits(appInfo, classLoader, containerSystemContext, appContext, lazyValidatorFactories, lazyValidators, isGeronimo); // Connectors for (final ConnectorInfo connector : appInfo.connectors) { final ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); try { // todo add undeployment code for these if (connector.resourceAdapter != null) { createResource(connector.resourceAdapter); } for (final ResourceInfo outbound : connector.outbound) { createResource(outbound); outbound.properties.setProperty("openejb.connector", "true"); // set it after as a marker but not as an attribute (no getOpenejb().setConnector(...)) } for (final MdbContainerInfo inbound : connector.inbound) { createContainer(inbound); } for (final ResourceInfo adminObject : connector.adminObject) { createResource(adminObject); } } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } } final List allDeployments = initEjbs(classLoader, appInfo, appContext, injections, new ArrayList(), null); if ("true".equalsIgnoreCase(SystemInstance.get() .getProperty(PROPAGATE_APPLICATION_EXCEPTIONS, appInfo.properties.getProperty(PROPAGATE_APPLICATION_EXCEPTIONS, "false")))) { propagateApplicationExceptions(appInfo, classLoader, allDeployments); } if ("true".equalsIgnoreCase(appInfo.properties.getProperty("openejb.cdi.activated", "true"))) { new CdiBuilder().build(appInfo, appContext, allDeployments); ensureWebBeansContext(appContext); appJndiContext.bind("app/BeanManager", appContext.getBeanManager()); appContext.getBindings().put("app/BeanManager", appContext.getBeanManager()); } // now cdi is started we can try to bind real validator factory and validator if (!isGeronimo) { bindValidators(containerSystemContext, lazyValidatorFactories, lazyValidators); } startEjbs(start, allDeployments); // App Client for (final ClientInfo clientInfo : appInfo.clients) { bindInjections(classLoader, containerSystemContext, injectionBuilder, clientInfo); } // WebApp final SystemInstance systemInstance = SystemInstance.get(); final WebAppBuilder webAppBuilder = systemInstance.getComponent(WebAppBuilder.class); if (webAppBuilder != null) { webAppBuilder.deployWebApps(appInfo, classLoader); } if (start) { final EjbResolver globalEjbResolver = systemInstance.getComponent(EjbResolver.class); if (globalEjbResolver != null) { globalEjbResolver.addAll(appInfo.ejbJars); } } // bind all global values on global context bindGlobals(appContext.getBindings()); // deploy MBeans for (final String mbean : appInfo.mbeans) { deployMBean(appContext.getWebBeansContext(), classLoader, mbean, appInfo.jmx, appInfo.appId); } for (final EjbJarInfo ejbJarInfo : appInfo.ejbJars) { for (final String mbean : ejbJarInfo.mbeans) { deployMBean(appContext.getWebBeansContext(), classLoader, mbean, appInfo.jmx, ejbJarInfo.moduleName); } } for (final ConnectorInfo connectorInfo : appInfo.connectors) { for (final String mbean : connectorInfo.mbeans) { deployMBean(appContext.getWebBeansContext(), classLoader, mbean, appInfo.jmx, appInfo.appId + ".add-lib"); } } postConstructResources(appInfo.resourceIds, classLoader, containerSystemContext, appContext); deployedApplications.put(appInfo.path, appInfo); resumePersistentSchedulers(appContext); systemInstance.fireEvent(new AssemblerAfterApplicationCreated(appInfo, appContext, allDeployments)); logger.info("createApplication.success", appInfo.path); return appContext; } catch (final ValidationException ve) { throw ve; } catch (final Throwable t) { try { destroyApplication(appInfo); } catch (final Exception e1) { logger.debug("createApplication.undeployFailed", e1, appInfo.path); } throw new OpenEJBException(messages.format("createApplication.failed", appInfo.path), t); } } private void bindInjections(final ClassLoader classLoader, final Context containerSystemContext, final InjectionBuilder injectionBuilder, final ClientInfo clientInfo) throws OpenEJBException, NamingException { // determine the injections final List clientInjections = injectionBuilder.buildInjections(clientInfo.jndiEnc); // build the enc final JndiEncBuilder jndiEncBuilder = new JndiEncBuilder(clientInfo.jndiEnc, clientInjections, "Bean", clientInfo.moduleId, null, clientInfo.uniqueId, classLoader, new Properties()); // if there is at least a remote client classes // or if there is no local client classes // then, we can set the client flag if (clientInfo.remoteClients.size() > 0 || clientInfo.localClients.size() == 0) { jndiEncBuilder.setClient(true); } jndiEncBuilder.setUseCrossClassLoaderRef(false); final Context context = jndiEncBuilder.build(JndiEncBuilder.JndiScope.comp); // Debug.printContext(context); containerSystemContext.bind("openejb/client/" + clientInfo.moduleId, context); if (clientInfo.path != null) { context.bind("info/path", clientInfo.path); } if (clientInfo.mainClass != null) { context.bind("info/mainClass", clientInfo.mainClass); } if (clientInfo.callbackHandler != null) { context.bind("info/callbackHandler", clientInfo.callbackHandler); } context.bind("info/injections", clientInjections); for (final String clientClassName : clientInfo.remoteClients) { containerSystemContext.bind("openejb/client/" + clientClassName, clientInfo.moduleId); } for (final String clientClassName : clientInfo.localClients) { containerSystemContext.bind("openejb/client/" + clientClassName, clientInfo.moduleId); logger.getChildLogger("client").info("createApplication.createLocalClient", clientClassName, clientInfo.moduleId); } } private void bindValidators(final Context containerSystemContext, final Map lazyValidatorFactories, final Map lazyValidators) throws OpenEJBException { for (final Entry lazyValidator : lazyValidators.entrySet()) { final String id = lazyValidator.getKey(); final ValidatorFactory factory = lazyValidatorFactories.get(lazyValidator.getKey()).getFactory(); try { final String factoryName = VALIDATOR_FACTORY_NAMING_CONTEXT + id; containerSystemContext.unbind(factoryName); containerSystemContext.bind(factoryName, factory); final String validatoryName = VALIDATOR_NAMING_CONTEXT + id; try { // do it after factory cause of TCKs which expects validator to be created later final Validator val = lazyValidator.getValue().getValidator(); containerSystemContext.unbind(validatoryName); containerSystemContext.bind(validatoryName, val); } catch (final Exception e) { logger.error(e.getMessage(), e); } } catch (final NameAlreadyBoundException e) { throw new OpenEJBException("ValidatorFactory already exists for module " + id, e); } catch (final Exception e) { throw new OpenEJBException(e); } } } private void loadPersistenceUnits(final AppInfo appInfo, final ClassLoader classLoader, final Context containerSystemContext, final AppContext appContext, final Map lazyValidatorFactories, final Map lazyValidators, final boolean isGeronimo) throws OpenEJBException { // try to not create N times the same validator for a single app final Map validatorFactoriesByConfig = new HashMap(); if (!isGeronimo) { // Bean Validation // ValidatorFactory needs to be put in the map sent to the entity manager factory // so it has to be constructed before final List vfs = listCommonInfoObjectsForAppInfo(appInfo); final Map validatorFactories = new HashMap(); for (final CommonInfoObject info : vfs) { final ComparableValidationConfig conf = new ComparableValidationConfig( info.validationInfo.providerClassName, info.validationInfo.messageInterpolatorClass, info.validationInfo.traversableResolverClass, info.validationInfo.constraintFactoryClass, info.validationInfo.propertyTypes, info.validationInfo.constraintMappings ); ValidatorFactory factory = validatorFactoriesByConfig.get(conf); if (factory == null) { try { // lazy cause of CDI :( final LazyValidatorFactory handler = new LazyValidatorFactory(classLoader, info.validationInfo); factory = (ValidatorFactory) Proxy.newProxyInstance( appContext.getClassLoader(), VALIDATOR_FACTORY_INTERFACES, handler); lazyValidatorFactories.put(info.uniqueId, handler); } catch (final ValidationException ve) { logger.warning("Failed to build the validation factory for module " + info.uniqueId, ve); continue; } validatorFactoriesByConfig.put(conf, factory); } else { lazyValidatorFactories.put(info.uniqueId, LazyValidatorFactory.class.cast(Proxy.getInvocationHandler(factory))); } validatorFactories.put(info.uniqueId, factory); } // validators bindings for (final Entry validatorFactory : validatorFactories.entrySet()) { final String id = validatorFactory.getKey(); final ValidatorFactory factory = validatorFactory.getValue(); try { containerSystemContext.bind(VALIDATOR_FACTORY_NAMING_CONTEXT + id, factory); final Validator validator; try { final LazyValidator lazyValidator = new LazyValidator(factory); validator = (Validator) Proxy.newProxyInstance(appContext.getClassLoader(), VALIDATOR_INTERFACES, lazyValidator); lazyValidators.put(id, lazyValidator); } catch (final Exception e) { logger.error(e.getMessage(), e); continue; } containerSystemContext.bind(VALIDATOR_NAMING_CONTEXT + id, validator); } catch (final NameAlreadyBoundException e) { throw new OpenEJBException("ValidatorFactory already exists for module " + id, e); } catch (final Exception e) { throw new OpenEJBException(e); } } validatorFactories.clear(); } // JPA - Persistence Units MUST be processed first since they will add ClassFileTransformers // to the class loader which must be added before any classes are loaded final Map units = new HashMap(); final PersistenceBuilder persistenceBuilder = new PersistenceBuilder(persistenceClassLoaderHandler); for (final PersistenceUnitInfo info : appInfo.persistenceUnits) { final ReloadableEntityManagerFactory factory; try { factory = persistenceBuilder.createEntityManagerFactory(info, classLoader, validatorFactoriesByConfig); containerSystem.getJNDIContext().bind(PERSISTENCE_UNIT_NAMING_CONTEXT + info.id, factory); units.put(info.name, PERSISTENCE_UNIT_NAMING_CONTEXT + info.id); } catch (final NameAlreadyBoundException e) { throw new OpenEJBException("PersistenceUnit already deployed: " + info.persistenceUnitRootUrl); } catch (final Exception e) { throw new OpenEJBException(e); } factory.register(); } logger.debug("Loaded peristence units: " + units); } private void checkForDuplicates(final AppInfo appInfo) throws DuplicateDeploymentIdException { final List used = new ArrayList(); for (final EjbJarInfo ejbJarInfo : appInfo.ejbJars) { for (final EnterpriseBeanInfo beanInfo : ejbJarInfo.enterpriseBeans) { if (containerSystem.getBeanContext(beanInfo.ejbDeploymentId) != null) { used.add(beanInfo.ejbDeploymentId); } } } if (used.size() > 0) { String message = logger.error("createApplication.appFailedDuplicateIds", appInfo.path); for (final String id : used) { logger.error("createApplication.deploymentIdInUse", id); message += "\n " + id; } throw new DuplicateDeploymentIdException(message); } } private void postConstructResources( final Set resourceIds, final ClassLoader classLoader, final Context containerSystemContext, final AppContext appContext) throws NamingException, OpenEJBException { final Thread thread = Thread.currentThread(); final ClassLoader oldCl = thread.getContextClassLoader(); try { thread.setContextClassLoader(classLoader); final List resourceList = config.facilities.resources; for (final ResourceInfo resourceInfo : resourceList) { if (!resourceIds.contains(resourceInfo.id)) { continue; } if (isTemplatizedResource(resourceInfo)) { continue; } try { Class clazz; try { clazz = classLoader.loadClass(resourceInfo.className); } catch (final ClassNotFoundException cnfe) { // custom classpath clazz = containerSystemContext.lookup(OPENEJB_RESOURCE_JNDI_PREFIX + resourceInfo.id).getClass(); } final boolean initialize = "true".equalsIgnoreCase(String.valueOf(resourceInfo.properties.remove("InitializeAfterDeployment"))); final AnnotationFinder finder = Proxy.isProxyClass(clazz) ? null : new AnnotationFinder(new ClassesArchive(ancestors(clazz))); final List postConstructs = finder == null ? Collections.emptyList() : finder.findAnnotatedMethods(PostConstruct.class); final List preDestroys = finder == null ? Collections.emptyList() : finder.findAnnotatedMethods(PreDestroy.class); resourceInfo.postConstructMethods = new ArrayList(); resourceInfo.preDestroyMethods = new ArrayList(); addMethodsToResourceInfo(resourceInfo.postConstructMethods, PostConstruct.class, postConstructs); addMethodsToResourceInfo(resourceInfo.preDestroyMethods, PreDestroy.class, preDestroys); CreationalContext creationalContext = null; Object originalResource = null; if (!postConstructs.isEmpty() || initialize) { originalResource = containerSystemContext.lookup(OPENEJB_RESOURCE_JNDI_PREFIX + resourceInfo.id); Object resource = originalResource; if (Reference.class.isInstance(resource)) { resource = unwrapReference(resource); this.bindResource(resourceInfo.id, resource, true); } try { // wire up CDI if (appContext != null) { final BeanManagerImpl beanManager = appContext.getWebBeansContext().getBeanManagerImpl(); if (beanManager.isInUse()) { creationalContext = beanManager.createCreationalContext(null); OWBInjector.inject(beanManager, resource, creationalContext); } } if (!"none".equals(resourceInfo.postConstruct)) { if (resourceInfo.postConstruct != null) { final Method p = clazz.getDeclaredMethod(resourceInfo.postConstruct); if (!p.isAccessible()) { SetAccessible.on(p); } p.invoke(resource); } for (final Method m : postConstructs) { if (!m.isAccessible()) { SetAccessible.on(m); } m.invoke(resource); } } } catch (final Exception e) { logger.fatal("Error calling @PostConstruct method on " + resource.getClass().getName()); throw new OpenEJBException(e); } } if (!"none".equals(resourceInfo.preDestroy)) { if (resourceInfo.preDestroy != null) { final Method p = clazz.getDeclaredMethod(resourceInfo.preDestroy); if (!p.isAccessible()) { SetAccessible.on(p); } preDestroys.add(p); } if (!preDestroys.isEmpty() || creationalContext != null) { final String name = OPENEJB_RESOURCE_JNDI_PREFIX + resourceInfo.id; if (originalResource == null) { originalResource = containerSystemContext.lookup(name); } this.bindResource(resourceInfo.id, new ResourceInstance(name, originalResource, preDestroys, creationalContext), true); } } // log unused now for these resources now we built the resource completely and @PostConstruct can have used injected properties if (resourceInfo.unsetProperties != null) { final Set unsetKeys = resourceInfo.unsetProperties.stringPropertyNames(); for (final String key : unsetKeys) { // don't use keySet to auto filter txMgr for instance and not real properties! unusedProperty(resourceInfo.id, logger, key); } } } catch (final Exception e) { logger.fatal("Error calling PostConstruct method on " + resourceInfo.id); logger.fatal("Resource " + resourceInfo.id + " could not be initialized. Application will be undeployed."); throw new OpenEJBException(e); } } } finally { thread.setContextClassLoader(oldCl); } } private void addMethodsToResourceInfo(final List list, final Class type, final List methodList) throws OpenEJBException { for (final Method method : methodList) { if (method.getParameterTypes().length > 0) { throw new OpenEJBException(type.getSimpleName() + " method " + method.getDeclaringClass().getName() + "." + method.getName() + " should have zero arguments"); } list.add(method.getName()); } } private static boolean isTemplatizedResource(final ResourceInfo resourceInfo) { // ~ container resource even if not 100% right return resourceInfo.className == null || resourceInfo.className.isEmpty(); } public static void mergeServices(final AppInfo appInfo) throws URISyntaxException { for (final ServiceInfo si : appInfo.services) { // used lazily by JaxWsServiceObjectFactory, we could do the same for resources if (!appInfo.properties.containsKey(si.id)) { final Map query = new HashMap(); if (si.types != null && !si.types.isEmpty()) { query.put("type", si.types.iterator().next()); } if (si.className != null) { query.put("class-name", si.className); } if (si.factoryMethod != null) { query.put("factory-name", si.factoryMethod); } if (si.constructorArgs != null && !si.constructorArgs.isEmpty()) { query.put("constructor", Join.join(",", si.constructorArgs)); } appInfo.properties.put(si.id, "new://Service?" + URISupport.createQueryString(query)); if (si.properties != null) { for (final String k : si.properties.stringPropertyNames()) { appInfo.properties.setProperty(si.id + "." + k, si.properties.getProperty(k)); } } } } } private static List listCommonInfoObjectsForAppInfo(final AppInfo appInfo) { final List vfs = new ArrayList( appInfo.clients.size() + appInfo.connectors.size() + appInfo.ejbJars.size() + appInfo.webApps.size()); for (final ClientInfo clientInfo : appInfo.clients) { vfs.add(clientInfo); } for (final ConnectorInfo connectorInfo : appInfo.connectors) { vfs.add(connectorInfo); } for (final EjbJarInfo ejbJarInfo : appInfo.ejbJars) { vfs.add(ejbJarInfo); } for (final WebAppInfo webAppInfo : appInfo.webApps) { vfs.add(webAppInfo); } return vfs; } public void bindGlobals(final Map bindings) throws NamingException { final Context containerSystemContext = containerSystem.getJNDIContext(); for (final Entry value : bindings.entrySet()) { final String path = value.getKey(); // keep only global bindings if (path.startsWith("module/") || path.startsWith("app/") || path.startsWith("comp/") || path.equalsIgnoreCase("global/dummy")) { continue; } // a bit weird but just to be consistent if user doesn't lookup directly the resource final Context lastContext = Contexts.createSubcontexts(containerSystemContext, path); try { lastContext.rebind(path.substring(path.lastIndexOf("/") + 1, path.length()), value.getValue()); } catch (final NameAlreadyBoundException nabe) { nabe.printStackTrace(); } containerSystemContext.rebind(path, value.getValue()); } } private void propagateApplicationExceptions(final AppInfo appInfo, final ClassLoader classLoader, final List allDeployments) { for (final BeanContext context : allDeployments) { if (BeanContext.Comp.class.equals(context.getBeanClass())) { continue; } for (final EjbJarInfo jar : appInfo.ejbJars) { for (final ApplicationExceptionInfo exception : jar.applicationException) { try { final Class exceptionClass = classLoader.loadClass(exception.exceptionClass); context.addApplicationException(exceptionClass, exception.rollback, exception.inherited); } catch (final Exception e) { // no-op: not a big deal since by jar config is respected, mainly means propagation didn't work because of classloader constraints } } } } } private void resumePersistentSchedulers(final AppContext appContext) { final Scheduler globalScheduler = SystemInstance.get().getComponent(Scheduler.class); final Collection schedulers = new ArrayList(); for (final BeanContext ejb : appContext.getBeanContexts()) { final Scheduler scheduler = ejb.get(Scheduler.class); if (scheduler == null || scheduler == globalScheduler || schedulers.contains(scheduler)) { continue; } schedulers.add(scheduler); try { scheduler.resumeAll(); } catch (final Exception e) { logger.warning("Failed to resume scheduler for " + ejb.getEjbName(), e); } } } public List initEjbs(final ClassLoader classLoader, final AppInfo appInfo, final AppContext appContext, final Set injections, final List allDeployments, final String webappId) throws OpenEJBException { final String globalTimersOn = SystemInstance.get().getProperty(OPENEJB_TIMERS_ON, "true"); final EjbJarBuilder ejbJarBuilder = new EjbJarBuilder(props, appContext); for (final EjbJarInfo ejbJar : appInfo.ejbJars) { boolean skip = false; if (!appInfo.webAppAlone) { if (webappId == null) { skip = ejbJar.webapp; // we look for the lib part of the ear so deploy only if not a webapp } else if (!ejbJar.webapp || (!ejbJar.moduleId.equals(webappId) && !ejbJar.properties.getProperty("openejb.ejbmodule.webappId", "-").equals(webappId))) { skip = true; // we look for a particular webapp deployment so deploy only if this webapp } } if (skip) { continue; } final HashMap deployments = ejbJarBuilder.build(ejbJar, injections, classLoader); final JaccPermissionsBuilder jaccPermissionsBuilder = new JaccPermissionsBuilder(); final PolicyContext policyContext = jaccPermissionsBuilder.build(ejbJar, deployments); jaccPermissionsBuilder.install(policyContext); final TransactionPolicyFactory transactionPolicyFactory = createTransactionPolicyFactory(ejbJar, classLoader); for (final BeanContext beanContext : deployments.values()) { beanContext.setTransactionPolicyFactory(transactionPolicyFactory); } final MethodTransactionBuilder methodTransactionBuilder = new MethodTransactionBuilder(); methodTransactionBuilder.build(deployments, ejbJar.methodTransactions); final MethodConcurrencyBuilder methodConcurrencyBuilder = new MethodConcurrencyBuilder(); methodConcurrencyBuilder.build(deployments, ejbJar.methodConcurrency); for (final BeanContext beanContext : deployments.values()) { containerSystem.addDeployment(beanContext); } //bind ejbs into global jndi jndiBuilder.build(ejbJar, deployments); // setup timers/asynchronous methods - must be after transaction attributes are set for (final BeanContext beanContext : deployments.values()) { if (beanContext.getComponentType() != BeanType.STATEFUL) { final Method ejbTimeout = beanContext.getEjbTimeout(); boolean timerServiceRequired = false; if (ejbTimeout != null) { // If user set the tx attribute to RequiresNew change it to Required so a new transaction is not started if (beanContext.getTransactionType(ejbTimeout) == TransactionType.RequiresNew) { beanContext.setMethodTransactionAttribute(ejbTimeout, TransactionType.Required); } timerServiceRequired = true; } for (final Iterator> it = beanContext.iteratorMethodContext(); it.hasNext(); ) { final Map.Entry entry = it.next(); final MethodContext methodContext = entry.getValue(); if (methodContext.getSchedules().size() > 0) { timerServiceRequired = true; final Method method = entry.getKey(); //TODO Need ? if (beanContext.getTransactionType(method) == TransactionType.RequiresNew) { beanContext.setMethodTransactionAttribute(method, TransactionType.Required); } } } if (timerServiceRequired && "true".equalsIgnoreCase(appInfo.properties.getProperty(OPENEJB_TIMERS_ON, globalTimersOn))) { // Create the timer final EjbTimerServiceImpl timerService = new EjbTimerServiceImpl(beanContext, newTimerStore(beanContext)); //Load auto-start timers final TimerStore timerStore = timerService.getTimerStore(); for (final Iterator> it = beanContext.iteratorMethodContext(); it.hasNext(); ) { final Map.Entry entry = it.next(); final MethodContext methodContext = entry.getValue(); for (final ScheduleData scheduleData : methodContext.getSchedules()) { timerStore.createCalendarTimer(timerService, (String) beanContext.getDeploymentID(), null, entry.getKey(), scheduleData.getExpression(), scheduleData.getConfig(), true); } } beanContext.setEjbTimerService(timerService); } else { beanContext.setEjbTimerService(new NullEjbTimerServiceImpl()); } } //set asynchronous methods transaction //TODO ??? for (final Iterator> it = beanContext.iteratorMethodContext(); it.hasNext(); ) { final Entry entry = it.next(); if (entry.getValue().isAsynchronous() && beanContext.getTransactionType(entry.getKey()) == TransactionType.RequiresNew) { beanContext.setMethodTransactionAttribute(entry.getKey(), TransactionType.Required); } } // if local bean or mdb generate proxy class now to avoid bottleneck on classloader later if (beanContext.isLocalbean() && !beanContext.getComponentType().isMessageDriven() && !beanContext.isDynamicallyImplemented()) { final List interfaces = new ArrayList(3); interfaces.add(Serializable.class); interfaces.add(IntraVmProxy.class); final BeanType type = beanContext.getComponentType(); if (BeanType.STATEFUL.equals(type) || BeanType.MANAGED.equals(type)) { interfaces.add(BeanContext.Removable.class); } beanContext.set( BeanContext.ProxyClass.class, new BeanContext.ProxyClass( beanContext, interfaces.toArray(new Class[interfaces.size()]) )); } } // process application exceptions for (final ApplicationExceptionInfo exceptionInfo : ejbJar.applicationException) { try { final Class exceptionClass = classLoader.loadClass(exceptionInfo.exceptionClass); for (final BeanContext beanContext : deployments.values()) { beanContext.addApplicationException(exceptionClass, exceptionInfo.rollback, exceptionInfo.inherited); } } catch (final ClassNotFoundException e) { logger.error("createApplication.invalidClass", e, exceptionInfo.exceptionClass, e.getMessage()); } } allDeployments.addAll(deployments.values()); } final List ejbs = sort(allDeployments); appContext.getBeanContexts().addAll(ejbs); return ejbs; } private TimerStore newTimerStore(final BeanContext beanContext) { for (final DeploymentContext context : Arrays.asList(beanContext, beanContext.getModuleContext(), beanContext.getModuleContext().getAppContext())) { final String timerStoreClass = context.getProperties().getProperty(TIMER_STORE_CLASS); if (timerStoreClass != null) { logger.info("Found timer class: " + timerStoreClass); try { final Class clazz = beanContext.getClassLoader().loadClass(timerStoreClass); try { final Constructor constructor = clazz.getConstructor(TransactionManager.class); return TimerStore.class.cast(constructor.newInstance(EjbTimerServiceImpl.getDefaultTransactionManager())); } catch (final Exception ignored) { return TimerStore.class.cast(clazz.newInstance()); } } catch (final Exception e) { logger.error("Failed to instantiate " + timerStoreClass + ", using default memory timer store"); } } } return new MemoryTimerStore(EjbTimerServiceImpl.getDefaultTransactionManager()); } public void startEjbs(final boolean start, final List allDeployments) throws OpenEJBException { // now that everything is configured, deploy to the container if (start) { SystemInstance.get().fireEvent(new BeforeStartEjbs(allDeployments)); final Collection toStart = new ArrayList(); // deploy for (final BeanContext deployment : allDeployments) { try { final Container container = deployment.getContainer(); if (container.getBeanContext(deployment.getDeploymentID()) == null) { container.deploy(deployment); if (!((String) deployment.getDeploymentID()).endsWith(".Comp") && !deployment.isHidden()) { logger.info("createApplication.createdEjb", deployment.getDeploymentID(), deployment.getEjbName(), container.getContainerID()); } if (logger.isDebugEnabled()) { for (final Map.Entry entry : deployment.getProperties().entrySet()) { logger.info("createApplication.createdEjb.property", deployment.getEjbName(), entry.getKey(), entry.getValue()); } } toStart.add(deployment); } } catch (final Throwable t) { throw new OpenEJBException("Error deploying '" + deployment.getEjbName() + "'. Exception: " + t.getClass() + ": " + t.getMessage(), t); } } // start for (final BeanContext deployment : toStart) { try { final Container container = deployment.getContainer(); container.start(deployment); if (!((String) deployment.getDeploymentID()).endsWith(".Comp") && !deployment.isHidden()) { logger.info("createApplication.startedEjb", deployment.getDeploymentID(), deployment.getEjbName(), container.getContainerID()); } } catch (final Throwable t) { throw new OpenEJBException("Error starting '" + deployment.getEjbName() + "'. Exception: " + t.getClass() + ": " + t.getMessage(), t); } } } } @SuppressWarnings("unchecked") private void deployMBean(final WebBeansContext wc, final ClassLoader cl, final String mbeanClass, final Properties appMbeans, final String id) { if (LocalMBeanServer.isJMXActive()) { final Class clazz; try { clazz = cl.loadClass(mbeanClass); } catch (final ClassNotFoundException e) { throw new OpenEJBRuntimeException(e); } // cdi can be off so init with null bean in this case final Bean bean; final BeanManager bm; if (wc == null) { bm = null; bean = null; } else { bm = wc.getBeanManagerImpl(); final Set> beans = bm.getBeans(clazz); bean = bm.resolve(beans); } // create the MBean instance with cdi if possible or manually otherwise final Object instance; final CreationalContext creationalContext; if (bean == null) { try { instance = clazz.newInstance(); } catch (final InstantiationException e) { logger.error("the mbean " + mbeanClass + " Failed to be registered because it Failed to be instantiated", e); return; } catch (final IllegalAccessException e) { logger.error("the mbean " + mbeanClass + " Failed to be registered because it Failed to be accessed", e); return; } creationalContext = null; } else { creationalContext = bm.createCreationalContext(bean); instance = bm.getReference(bean, clazz, creationalContext); } final MBeanServer server = LocalMBeanServer.get(); try { final ObjectName leaf = new ObjectNameBuilder("openejb.user.mbeans") .set("application", id) .set("group", clazz.getPackage().getName()) .set("name", clazz.getSimpleName()) .build(); server.registerMBean(new DynamicMBeanWrapper(wc, instance), leaf); appMbeans.put(mbeanClass, leaf.getCanonicalName()); if (creationalContext != null && (bean.getScope() == null || Dependent.class.equals(bean.getScope()))) { creationalContextForAppMbeans.put(leaf, creationalContext); } logger.info("Deployed MBean(" + leaf.getCanonicalName() + ")"); } catch (final Exception e) { logger.error("the mbean " + mbeanClass + " Failed to be registered", e); } } } private void ensureWebBeansContext(final AppContext appContext) { WebBeansContext webBeansContext = appContext.get(WebBeansContext.class); if (webBeansContext == null) { webBeansContext = appContext.getWebBeansContext(); } else { if (null == appContext.getWebBeansContext()) { appContext.setWebBeansContext(webBeansContext); } return; } if (webBeansContext == null) { final Map, Object> services = new HashMap, Object>(); services.put(JNDIService.class, new OpenEJBJndiService()); services.put(AppContext.class, appContext); services.put(TransactionService.class, new OpenEJBTransactionService()); services.put(ScannerService.class, new CdiScanner()); services.put(ELAdaptor.class, new CustomELAdapter(appContext)); services.put(LoaderService.class, new OptimizedLoaderService()); final Properties properties = new Properties(); properties.setProperty(org.apache.webbeans.spi.SecurityService.class.getName(), ManagedSecurityService.class.getName()); webBeansContext = new WebBeansContext(services, properties); webBeansContext.registerService(ContextsService.class, new CdiAppContextsService(webBeansContext, true)); webBeansContext.registerService(ResourceInjectionService.class, new CdiResourceInjectionService(webBeansContext)); appContext.setCdiEnabled(false); final Object obj = services.get(TransactionService.class); if (null != obj) { OpenEJBTransactionService.class.cast(obj).setWebBeansContext(webBeansContext); } appContext.set(WebBeansContext.class, webBeansContext); appContext.setWebBeansContext(webBeansContext); } } private TransactionPolicyFactory createTransactionPolicyFactory(final EjbJarInfo ejbJar, final ClassLoader classLoader) { TransactionPolicyFactory factory = null; final Object value = ejbJar.properties.get(TransactionPolicyFactory.class.getName()); if (TransactionPolicyFactory.class.isInstance(value)) { factory = TransactionPolicyFactory.class.cast(value); } else if (String.class.isInstance(value)) { try { final String[] parts = String.class.cast(value).split(":", 2); final ResourceFinder finder = new ResourceFinder("META-INF", classLoader); final Map> plugins = finder.mapAvailableImplementations(TransactionPolicyFactory.class); final Class clazz = plugins.get(parts[0]); if (clazz != null) { if (parts.length == 1) { factory = clazz.getConstructor(String.class).newInstance(parts[1]); } else { factory = clazz.newInstance(); } } } catch (final Exception ignored) { // couldn't determine the plugins, which isn't fatal } } if (factory == null) { factory = new JtaTransactionPolicyFactory(transactionManager); } return factory; } private static List sort(List deployments) { // Sort all the singletons to the back of the list. We want to make sure // all non-singletons are created first so that if a singleton refers to them // they are available. Collections.sort(deployments, new Comparator() { @Override public int compare(final BeanContext a, final BeanContext b) { final int aa = a.getComponentType() == BeanType.SINGLETON ? 1 : 0; final int bb = b.getComponentType() == BeanType.SINGLETON ? 1 : 0; return aa - bb; } }); // Sort all the beans with references to the back of the list. Beans // without references to ther beans will be deployed first. deployments = References.sort(deployments, new References.Visitor() { @Override public String getName(final BeanContext t) { return (String) t.getDeploymentID(); } @Override public Set getReferences(final BeanContext t) { return t.getDependsOn(); } }); // Now Sort all the MDBs to the back of the list. The Resource Adapter // may attempt to use the MDB on endpointActivation and the MDB may have // references to other ejbs that would need to be available first. Collections.sort(deployments, new Comparator() { @Override public int compare(final BeanContext a, final BeanContext b) { final int aa = a.getComponentType() == BeanType.MESSAGE_DRIVEN ? 1 : 0; final int bb = b.getComponentType() == BeanType.MESSAGE_DRIVEN ? 1 : 0; return aa - bb; } }); return deployments; } @Override public void destroy() { final ReentrantLock l = lock; l.lock(); try { SystemInstance.get().fireEvent(new ContainerSystemPreDestroy()); try { EjbTimerServiceImpl.shutdown(); } catch (final Exception e) { logger.warning("Unable to shutdown scheduler", e); } logger.debug("Undeploying Applications"); final Assembler assembler = this; final List deployedApps = new ArrayList(assembler.getDeployedApplications()); Collections.reverse(deployedApps); // if an app relies on the previous one it surely relies on it too at undeploy time for (final AppInfo appInfo : deployedApps) { try { assembler.destroyApplication(appInfo.path); } catch (final UndeployException e) { logger.error("Undeployment failed: " + appInfo.path, e); } catch (final NoSuchApplicationException e) { //Ignore } } final Iterator it = containerObjectNames.iterator(); final MBeanServer server = LocalMBeanServer.get(); while (it.hasNext()) { try { server.unregisterMBean(it.next()); } catch (final Exception ignored) { // no-op } it.remove(); } try { remoteResourceMonitor.unregister(); } catch (final Exception ignored) { // no-op } NamingEnumeration namingEnumeration = null; try { namingEnumeration = containerSystem.getJNDIContext().listBindings("openejb/Resource"); } catch (final NamingException ignored) { // no resource adapters were created } destroyResourceTree("openejb/Resource", namingEnumeration); try { containerSystem.getJNDIContext().unbind("java:global"); } catch (final NamingException ignored) { // no-op } SystemInstance.get().removeComponent(OpenEjbConfiguration.class); SystemInstance.get().removeComponent(JtaEntityManagerRegistry.class); SystemInstance.get().removeComponent(TransactionSynchronizationRegistry.class); SystemInstance.get().removeComponent(EjbResolver.class); SystemInstance.get().fireEvent(new AssemblerDestroyed()); SystemInstance.reset(); } finally { l.unlock(); } } //TODO: add Jndi name to DestroyingResource private Collection destroyResourceTree(final String name, final NamingEnumeration namingEnumeration) { final List resources = new LinkedList(); while (namingEnumeration != null && namingEnumeration.hasMoreElements()) { final Binding binding = namingEnumeration.nextElement(); final String boundName = name + "/" + binding.getName(); String id = boundName; if (id.startsWith(OPENEJB_RESOURCE_JNDI_PREFIX)) { id = id.substring(OPENEJB_RESOURCE_JNDI_PREFIX.length()); } final Object object = binding.getObject(); if (Context.class.isInstance(object)) { try { final NamingEnumeration bindings = Context.class.cast(object).listBindings(""); resources.addAll(destroyResourceTree(boundName, bindings)); } catch (final NamingException e) { logger.error("Error removing bindings from " + boundName, e); } } else if (LazyResource.class.isInstance(object)) { removeResourceInfo(id); try { containerSystem.getJNDIContext().unbind(boundName); } catch (final NamingException e) { logger.error("Error unbinding " + boundName, e); } } else { resources.add(new DestroyingResource(binding.getName(), binding.getClassName(), object)); } } Collections.sort(resources, new Comparator() { // end by destroying RA after having closed CF pool for jms for instance @Override public int compare(final DestroyingResource o1, final DestroyingResource o2) { if (ResourceAdapter.class.isInstance(o2.instance) && !ResourceAdapter.class.isInstance(o1.instance)) { return -1; } return 1; } }); for (final DestroyingResource resource : resources) { try { destroyResource(resource.name, resource.clazz, resource.instance); } catch (final Throwable th) { logger.debug(th.getMessage(), th); } } return resources; } private void destroyResource(final String name, final String className, final Object object) { if (ResourceAdapterReference.class.isInstance(object)) { final ResourceAdapterReference resourceAdapter = ResourceAdapterReference.class.cast(object); try { logger.info("Stopping ResourceAdapter: " + name); if (logger.isDebugEnabled()) { logger.debug("Stopping ResourceAdapter: " + className); } if (null != resourceAdapter.pool && ExecutorService.class.isInstance(resourceAdapter.pool)) { final ExecutorService es = ExecutorService.class.cast(resourceAdapter.pool); es.shutdownNow(); } resourceAdapter.ra.stop(); } catch (final Throwable t) { logger.fatal("ResourceAdapter Shutdown Failed: " + name, t); } } else if (ResourceAdapter.class.isInstance(object)) { final ResourceAdapter resourceAdapter = ResourceAdapter.class.cast(object); try { logger.info("Stopping ResourceAdapter: " + name); if (logger.isDebugEnabled()) { logger.debug("Stopping ResourceAdapter: " + className); } resourceAdapter.stop(); } catch (final Throwable t) { logger.fatal("ResourceAdapter Shutdown Failed: " + name, t); } } else if (DataSourceFactory.knows(object)) { int timeout; try { timeout = Integer.parseInt(SystemInstance.get().getProperty(TOMEE_DATASOURCE_DESTROY_TIMEOUT, "1000")); if (timeout < 50) { logger.warning(TOMEE_DATASOURCE_DESTROY_TIMEOUT + " must be at least 50"); timeout = 50; } if (timeout > 30000) { timeout = 30000; logger.warning(TOMEE_DATASOURCE_DESTROY_TIMEOUT + " must not be greater than 30000"); } } catch (final Exception e) { timeout = 1000; } final CountDownLatch latch = new CountDownLatch(1); final Thread thread = new Thread("DataSource-Shutdown-" + name) { @Override public void run() { try { DataSourceFactory.destroy(object); logger.info("Closed DataSource: " + name); } catch (final Throwable t) { //Ignore } finally { latch.countDown(); } } }; thread.start(); boolean closed; try { closed = latch.await(timeout, TimeUnit.MILLISECONDS); } catch (final InterruptedException e) { closed = false; } if (!closed) { logger.warning("Failed to close DataSource within " + timeout + "ms: " + name); } } else if (ConnectorReference.class.isInstance(object)) { final ConnectorReference cr = ConnectorReference.class.cast(object); try { final ConnectionManager cm = cr.getConnectionManager(); if (AbstractConnectionManager.class.isInstance(cm)) { AbstractConnectionManager.class.cast(cm).doStop(); } } catch (final Exception e) { logger.debug("Not processing resource on destroy: " + className, e); } } else if (DestroyableResource.class.isInstance(object)) { try { DestroyableResource.class.cast(object).destroyResource(); } catch (final RuntimeException e) { logger.error(e.getMessage(), e); } } else if (logger.isDebugEnabled() && !DataSource.class.isInstance(object)) { logger.debug("Not processing resource on destroy: " + className); } callPreDestroy(name, object); removeResourceInfo(name); } private void callPreDestroy(final String name, final Object object) { if (object == null) { return; } if (ResourceInstance.class.isInstance(object)) { ResourceInstance.class.cast(object).destroyResource(); return; } final Class objectClass = object.getClass(); final ResourceInfo ri = findResourceInfo(name); if (ri == null) { return; } final Set destroyMethods = new HashSet(); if (ri.preDestroy != null) { destroyMethods.add(ri.preDestroy); } if (ri.preDestroyMethods != null && ri.preDestroyMethods.size() > 0) { destroyMethods.addAll(ri.preDestroyMethods); } for (final String destroyMethod : destroyMethods) { try { final Method p = objectClass.getDeclaredMethod(destroyMethod); if (!p.isAccessible()) { SetAccessible.on(p); } p.invoke(object); } catch (Exception e) { logger.error("Unable to call pre destroy method " + destroyMethod + " on " + objectClass.getName() + ". Continuing with resource destruction.", e); } } } private ResourceInfo findResourceInfo(final String name) { final List resourceInfos = config.facilities.resources; for (final ResourceInfo resourceInfo : resourceInfos) { if (resourceInfo.id.equals(name)) { return resourceInfo; } } return null; } public void removeResourceInfo(final String name) { try { //Ensure ResourceInfo for this resource is removed final OpenEjbConfiguration configuration = SystemInstance.get().getComponent(OpenEjbConfiguration.class); if (configuration != null) { final Iterator iterator = configuration.facilities.resources.iterator(); while (iterator.hasNext()) { final ResourceInfo info = iterator.next(); if (name.equals(info.id)) { iterator.remove(); break; } } } } catch (final Exception e) { logger.debug("Failed to purge resource on destroy: " + e.getMessage()); } } private static Object unwrapReference(final Object object) { Object o = object; while (o != null && Reference.class.isInstance(o)) { try { o = Reference.class.cast(o).getObject(); } catch (final NamingException e) { // break } } if (o == null) { o = object; } return o; } public void destroyApplication(final String filePath) throws UndeployException, NoSuchApplicationException { final ReentrantLock l = lock; l.lock(); try { final AppInfo appInfo = deployedApplications.remove(filePath); if (appInfo == null) { throw new NoSuchApplicationException(filePath); } destroyApplication(appInfo); } finally { l.unlock(); } } public void destroyApplication(final AppContext appContext) throws UndeployException { final ReentrantLock l = lock; l.lock(); try { final AppInfo appInfo = deployedApplications.remove(appContext.getId()); if (appInfo == null) { throw new IllegalStateException(String.format("Cannot find AppInfo for app: %s", appContext.getId())); } destroyApplication(appInfo); } finally { l.unlock(); } } public void destroyApplication(final AppInfo appInfo) throws UndeployException { final ReentrantLock l = lock; l.lock(); try { deployedApplications.remove(appInfo.path); logger.info("destroyApplication.start", appInfo.path); final AppContext appContext = containerSystem.getAppContext(appInfo.appId); if (null == appContext) { logger.warning("Application id '" + appInfo.appId + "' not found in: " + Arrays.toString(containerSystem.getAppContextKeys())); return; } final Context globalContext = containerSystem.getJNDIContext(); SystemInstance.get().fireEvent(new AssemblerBeforeApplicationDestroyed(appInfo, appContext)); final Map cb = appContext.getBindings(); for (final Entry value : cb.entrySet()) { String path = value.getKey(); if (path.startsWith("global")) { path = "java:" + path; } if (!path.startsWith("java:global")) { continue; } unbind(globalContext, path); unbind(globalContext, "openejb/global/" + path.substring("java:".length())); unbind(globalContext, path.substring("java:global".length())); } if (appInfo.appId != null && !appInfo.appId.isEmpty() && !"openejb".equals(appInfo.appId)) { unbind(globalContext, "global/" + appInfo.appId); unbind(globalContext, appInfo.appId); } final EjbResolver globalResolver = new EjbResolver(null, EjbResolver.Scope.GLOBAL); for (final AppInfo info : deployedApplications.values()) { globalResolver.addAll(info.ejbJars); } SystemInstance.get().setComponent(EjbResolver.class, globalResolver); final UndeployException undeployException = new UndeployException(messages.format("destroyApplication.failed", appInfo.path)); final WebAppBuilder webAppBuilder = SystemInstance.get().getComponent(WebAppBuilder.class); if (webAppBuilder != null && !appInfo.webAppAlone) { try { webAppBuilder.undeployWebApps(appInfo); } catch (final Exception e) { undeployException.getCauses().add(new Exception("App: " + appInfo.path + ": " + e.getMessage(), e)); } } // get all of the ejb deployments List deployments = new ArrayList(); for (final EjbJarInfo ejbJarInfo : appInfo.ejbJars) { for (final EnterpriseBeanInfo beanInfo : ejbJarInfo.enterpriseBeans) { final String deploymentId = beanInfo.ejbDeploymentId; final BeanContext beanContext = containerSystem.getBeanContext(deploymentId); if (beanContext == null) { undeployException.getCauses().add(new Exception("deployment not found: " + deploymentId)); } else { deployments.add(beanContext); } } } // Just as with startup we need to get things in an // order that respects the singleton @DependsOn information // Theoretically if a Singleton depends on something in its // @PostConstruct, it can depend on it in its @PreDestroy. // Therefore we want to make sure that if A dependsOn B, // that we destroy A first then B so that B will still be // usable in the @PreDestroy method of A. // Sort them into the original starting order deployments = sort(deployments); // reverse that to get the stopping order Collections.reverse(deployments); // Stop for (final BeanContext bc : deployments) { final String deploymentID = String.valueOf(bc.getDeploymentID()); try { final Container container = bc.getContainer(); container.stop(bc); } catch (final Throwable t) { undeployException.getCauses().add(new Exception("bean: " + deploymentID + ": " + t.getMessage(), t)); } } // Undeploy for (final BeanContext bean : deployments) { final String deploymentID = String.valueOf(bean.getDeploymentID()); try { final Container container = bean.getContainer(); container.undeploy(bean); bean.setContainer(null); } catch (final Throwable t) { undeployException.getCauses().add(new Exception("bean: " + deploymentID + ": " + t.getMessage(), t)); } finally { bean.setDestroyed(true); } } if (webAppBuilder != null && appInfo.webAppAlone) { // now that EJB are stopped we can undeploy webapps try { webAppBuilder.undeployWebApps(appInfo); } catch (final Exception e) { undeployException.getCauses().add(new Exception("App: " + appInfo.path + ": " + e.getMessage(), e)); } } // get the client ids final List clientIds = new ArrayList(); for (final ClientInfo clientInfo : appInfo.clients) { clientIds.add(clientInfo.moduleId); for (final String className : clientInfo.localClients) { clientIds.add(className); } for (final String className : clientInfo.remoteClients) { clientIds.add(className); } } for (final WebContext webContext : appContext.getWebContexts()) { containerSystem.removeWebContext(webContext); } final ClassLoader classLoader = appContext.getClassLoader(); TldScanner.forceCompleteClean(classLoader); // Clear out naming for all components first for (final BeanContext deployment : deployments) { final String deploymentID = String.valueOf(deployment.getDeploymentID()); try { containerSystem.removeBeanContext(deployment); } catch (final Throwable t) { undeployException.getCauses().add(new Exception(deploymentID, t)); } final JndiBuilder.Bindings bindings = deployment.get(JndiBuilder.Bindings.class); if (bindings != null) { for (final String name : bindings.getBindings()) { try { globalContext.unbind(name); } catch (final Throwable t) { undeployException.getCauses().add(new Exception("bean: " + deploymentID + ": " + t.getMessage(), t)); } } } } // stop this executor only now since @PreDestroy can trigger some stop events final AsynchronousPool pool = appContext.get(AsynchronousPool.class); if (pool != null) { pool.stop(); } for (final CommonInfoObject jar : listCommonInfoObjectsForAppInfo(appInfo)) { try { globalContext.unbind(VALIDATOR_FACTORY_NAMING_CONTEXT + jar.uniqueId); globalContext.unbind(VALIDATOR_NAMING_CONTEXT + jar.uniqueId); } catch (final NamingException e) { if (EjbJarInfo.class.isInstance(jar)) { undeployException.getCauses().add(new Exception("validator: " + jar.uniqueId + ": " + e.getMessage(), e)); } // else an error but not that important } } try { if (IvmContext.class.isInstance(globalContext)) { final IvmContext ivmContext = IvmContext.class.cast(globalContext); ivmContext.prune("openejb/Deployment"); ivmContext.prune("openejb/local"); ivmContext.prune("openejb/remote"); ivmContext.prune("openejb/global"); } } catch (final NamingException e) { undeployException.getCauses().add(new Exception("Unable to prune openejb/Deployments and openejb/local namespaces, this could cause future deployments to fail.", e)); } deployments.clear(); for (final String clientId : clientIds) { try { globalContext.unbind("/openejb/client/" + clientId); } catch (final Throwable t) { undeployException.getCauses().add(new Exception("client: " + clientId + ": " + t.getMessage(), t)); } } // mbeans final MBeanServer server = LocalMBeanServer.get(); for (final Object objectName : appInfo.jmx.values()) { try { final ObjectName on = new ObjectName((String) objectName); if (server.isRegistered(on)) { server.unregisterMBean(on); } final CreationalContext cc = creationalContextForAppMbeans.remove(on); if (cc != null) { cc.release(); } } catch (final InstanceNotFoundException e) { logger.warning("Failed to unregister " + objectName + " because the mbean was not found", e); } catch (final MBeanRegistrationException e) { logger.warning("Failed to unregister " + objectName, e); } catch (final MalformedObjectNameException mone) { logger.warning("Failed to unregister because the ObjectName is malformed: " + objectName, mone); } } // destroy PUs before resources since the JPA provider can use datasources for (final PersistenceUnitInfo unitInfo : appInfo.persistenceUnits) { try { final Object object = globalContext.lookup(PERSISTENCE_UNIT_NAMING_CONTEXT + unitInfo.id); globalContext.unbind(PERSISTENCE_UNIT_NAMING_CONTEXT + unitInfo.id); // close EMF so all resources are released final ReloadableEntityManagerFactory remf = (ReloadableEntityManagerFactory) object; remf.close(); persistenceClassLoaderHandler.destroy(unitInfo.id); remf.unregister(); } catch (final Throwable t) { undeployException.getCauses().add(new Exception("persistence-unit: " + unitInfo.id + ": " + t.getMessage(), t)); } } for (final String id : appInfo.resourceAliases) { final String name = OPENEJB_RESOURCE_JNDI_PREFIX + id; ContextualJndiReference.followReference.set(false); try { final Object object; try { object = globalContext.lookup(name); } finally { ContextualJndiReference.followReference.remove(); } if (ContextualJndiReference.class.isInstance(object)) { final ContextualJndiReference contextualJndiReference = ContextualJndiReference.class.cast(object); contextualJndiReference.removePrefix(appContext.getId()); if (contextualJndiReference.hasNoMorePrefix()) { globalContext.unbind(name); } // else not the last deployed application to use this resource so keep it } else { globalContext.unbind(name); } } catch (final NamingException e) { logger.warning("Failed to unbind resource '{0}'", id); } } for (final String id : appInfo.resourceIds) { final String name = OPENEJB_RESOURCE_JNDI_PREFIX + id; try { destroyLookedUpResource(globalContext, id, name); } catch (final NamingException e) { logger.warning("Failed to unbind resource '{0}'", id); } } for (final ConnectorInfo connector : appInfo.connectors) { if (connector.resourceAdapter == null || connector.resourceAdapter.id == null) { continue; } final String name = OPENEJB_RESOURCE_JNDI_PREFIX + connector.resourceAdapter.id; try { destroyLookedUpResource(globalContext, connector.resourceAdapter.id, name); } catch (final NamingException e) { logger.warning("Failed to unbind resource '{0}'", connector); } for (final ResourceInfo outbound : connector.outbound) { try { destroyLookedUpResource(globalContext, outbound.id, OPENEJB_RESOURCE_JNDI_PREFIX + outbound.id); } catch (final Exception e) { // no-op } } for (final ResourceInfo outbound : connector.adminObject) { try { destroyLookedUpResource(globalContext, outbound.id, OPENEJB_RESOURCE_JNDI_PREFIX + outbound.id); } catch (final Exception e) { // no-op } } for (final MdbContainerInfo container : connector.inbound) { try { containerSystem.removeContainer(container.id); config.containerSystem.containers.remove(container); this.containerSystem.getJNDIContext().unbind(JAVA_OPENEJB_NAMING_CONTEXT + container.service + "/" + container.id); } catch (final Exception e) { // no-op } } } final WebBeansContext webBeansContext = appContext.getWebBeansContext(); if (webBeansContext != null) { final ClassLoader old = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); try { webBeansContext.getService(ContainerLifecycle.class).stopApplication(null); } finally { Thread.currentThread().setContextClassLoader(old); } } containerSystem.removeAppContext(appInfo.appId); if (!appInfo.properties.containsKey("tomee.destroying")) { // destroy tomee classloader after resources cleanup try { final Method m = classLoader.getClass().getMethod("internalStop"); m.invoke(classLoader); } catch (final NoSuchMethodException nsme) { // no-op } catch (final Exception e) { logger.error("error stopping classloader of webapp " + appInfo.appId, e); } ClassLoaderUtil.cleanOpenJPACache(classLoader); } ClassLoaderUtil.destroyClassLoader(appInfo.appId, appInfo.path); if (undeployException.getCauses().size() > 0) { throw undeployException; } Assembler.resetSlf4j(logger); Logger.configure(true); logger.info("destroyApplication.success", appInfo.path); } finally { l.unlock(); } } private static void resetSlf4j(final Logger logger) { final Method m = Assembler.SLF4J_RESET.get(); if (null != m) { try { m.invoke(null); logger.info("Reset slf4j on application undeployment"); } catch (final InvocationTargetException e) { logger.warning("Assembler.resetSlf4j: " + e.getMessage()); } catch (final IllegalAccessException e) { logger.warning("Assembler.resetSlf4j: " + e.getMessage()); } } } private void destroyLookedUpResource(final Context globalContext, final String id, final String name) throws NamingException { // check to see if the resource is a LazyObjectReference that has not been initialized // if it is, we'll remove the LazyObjectReference, rather than do a lookup causing it // to be instantiated final String ctx = name.substring(0, name.lastIndexOf("/")); final String objName = name.substring(ctx.length() + 1); final NamingEnumeration bindings = globalContext.listBindings(ctx); while (bindings.hasMoreElements()) { final Binding binding = bindings.nextElement(); if (!binding.getName().equals(objName)) { continue; } if (!LazyObjectReference.class.isInstance(binding.getObject())) { continue; } final LazyObjectReference ref = LazyObjectReference.class.cast(binding.getObject()); if (!ref.isInitialized()) { globalContext.unbind(name); removeResourceInfo(id); return; } } // otherwise, look the object up and remove it final Object object = globalContext.lookup(name); final String clazz; if (object == null) { // should it be possible? clazz = "?"; } else { clazz = object.getClass().getName(); } destroyResource(id, clazz, object); globalContext.unbind(name); } private void unbind(final Context context, final String name) { try { context.unbind(name); } catch (final NamingException e) { // no-op } } public ClassLoader createAppClassLoader(final AppInfo appInfo) throws OpenEJBException, IOException { final Set jars = new HashSet(); for (final EjbJarInfo info : appInfo.ejbJars) { if (info.path != null) { jars.add(toUrl(info.path)); } } for (final ClientInfo info : appInfo.clients) { if (info.path != null) { jars.add(toUrl(info.path)); } } for (final ConnectorInfo info : appInfo.connectors) { for (final String jarPath : info.libs) { jars.add(toUrl(jarPath)); } } for (final String jarPath : appInfo.libs) { jars.add(toUrl(jarPath)); } // add openejb-jpa-integration if the jpa provider is in lib/ if (appInfo.libs.size() > 0) { // the test could be enhanced try { final File jpaIntegrationFile = JarLocation.jarLocation(MakeTxLookup.class); final URL url = jpaIntegrationFile.toURI().toURL(); if (!jars.contains(url)) { // could have been done before (webapp enrichment or manually for instance) jars.add(url); } } catch (final RuntimeException re) { logger.warning("Failed to find open-jpa-integration jar"); } } final ClassLoaderEnricher component = SystemInstance.get().getComponent(ClassLoaderEnricher.class); if (component != null) { jars.addAll(Arrays.asList(component.applicationEnrichment())); } // Create the class loader final ClassLoader parent = ParentClassLoaderFinder.Helper.get(); final String prefix; if (appInfo.webAppAlone) { prefix = "WEB-INF/"; } else { prefix = "META-INF/"; } final ClassLoaderConfigurer configurer1 = QuickJarsTxtParser.parse(new File(appInfo.path, prefix + QuickJarsTxtParser.FILE_NAME)); final ClassLoaderConfigurer configurer2 = ClassLoaderUtil.configurer(appInfo.appId); if (configurer1 != null || configurer2 != null) { final ClassLoaderConfigurer configurer = new CompositeClassLoaderConfigurer(configurer1, configurer2); ClassLoaderConfigurer.Helper.configure(jars, configurer); } final URL[] filtered = jars.toArray(new URL[jars.size()]); // some lib (DS for instance) rely on AppClassLoader for CDI bean manager usage (common for tests cases where you // try to get the app BM from the AppClassLoader having stored it in a map). // since we don't really need to create a classloader here when starting from classpath just let skip this step if (skipLoaderIfPossible) { // TODO: maybe use a boolean to know if all urls comes from the classpath to avoid this validation if ("classpath.ear".equals(appInfo.appId)) { return parent; } final Collection urls = new HashSet(); for (final URL url : ClassLoaders.findUrls(parent)) { // need to convert it to file since urls can be file:/xxx or jar:file:///xxx try { urls.add(URLs.toFile(url).getCanonicalFile()); } catch (final Exception error) { if (logger.isDebugEnabled()) { logger.debug("Failed to determine url for: " + url.toExternalForm(), error); } } } boolean allIsIntheClasspath = true; for (final URL url : filtered) { try { if (!urls.contains(URLs.toFile(url).getCanonicalFile())) { allIsIntheClasspath = false; if (logger.isDebugEnabled()) { logger.debug(url.toExternalForm() + " (" + URLs.toFile(url) + ") is not in the classloader so we'll create a dedicated classloader for this app"); } break; } } catch (final Exception ignored) { allIsIntheClasspath = false; if (logger.isDebugEnabled()) { logger.debug(url.toExternalForm() + " (" + URLs.toFile(url) + ") is not in the classloader", ignored); } break; } } if (allIsIntheClasspath) { logger.info("Not creating another application classloader for " + appInfo.appId); return parent; } else if (logger.isDebugEnabled()) { logger.debug("Logging all urls from the app since we don't skip the app classloader creation:"); for (final URL url : filtered) { logger.debug(" -> " + url.toExternalForm()); } logger.debug("Logging all urls from the classloader since we don't skip the app classloader creation:"); for (final File url : urls) { logger.debug(" -> " + url.getAbsolutePath()); } } } logger.info("Creating dedicated application classloader for " + appInfo.appId); if (!appInfo.delegateFirst) { return ClassLoaderUtil.createClassLoader(appInfo.path, filtered, parent); } return ClassLoaderUtil.createClassLoaderFirst(appInfo.path, filtered, parent); } public void createExternalContext(final JndiContextInfo contextInfo) throws OpenEJBException { logger.getChildLogger("service").info("createService", contextInfo.service, contextInfo.id, contextInfo.className); final InitialContext initialContext; try { initialContext = new InitialContext(contextInfo.properties); } catch (final NamingException ne) { throw new OpenEJBException(String.format("JndiProvider(id=\"%s\") could not be created. Failed to create the InitialContext using the supplied properties", contextInfo.id), ne); } try { containerSystem.getJNDIContext().bind("openejb/remote_jndi_contexts/" + contextInfo.id, initialContext); } catch (final NamingException e) { throw new OpenEJBException("Cannot bind " + contextInfo.service + " with id " + contextInfo.id, e); } // Update the config tree config.facilities.remoteJndiContexts.add(contextInfo); logger.getChildLogger("service").debug("createService.success", contextInfo.service, contextInfo.id, contextInfo.className); } public void createContainer(final ContainerInfo serviceInfo) throws OpenEJBException { final ObjectRecipe serviceRecipe = createRecipe(serviceInfo); serviceRecipe.setProperty("id", serviceInfo.id); serviceRecipe.setProperty("transactionManager", props.get(TransactionManager.class.getName())); serviceRecipe.setProperty("securityService", props.get(SecurityService.class.getName())); serviceRecipe.setProperty("properties", new UnsetPropertiesRecipe()); // MDB container has a resource adapter string name that // must be replaced with the real resource adapter instance replaceResourceAdapterProperty(serviceRecipe); final Object service = serviceRecipe.create(); logUnusedProperties(serviceRecipe, serviceInfo); final Class interfce = serviceInterfaces.get(serviceInfo.service); checkImplementation(interfce, service.getClass(), serviceInfo.service, serviceInfo.id); bindService(serviceInfo, service); setSystemInstanceComponent(interfce, service); props.put(interfce.getName(), service); props.put(serviceInfo.service, service); props.put(serviceInfo.id, service); containerSystem.addContainer(serviceInfo.id, (Container) service); // Update the config tree config.containerSystem.containers.add(serviceInfo); logger.getChildLogger("service").debug("createService.success", serviceInfo.service, serviceInfo.id, serviceInfo.className); if (Container.class.isInstance(service) && LocalMBeanServer.isJMXActive()) { final ObjectName objectName = ObjectNameBuilder.uniqueName("containers", serviceInfo.id, service); try { LocalMBeanServer.get().registerMBean(new DynamicMBeanWrapper(new JMXContainer(serviceInfo, (Container) service)), objectName); containerObjectNames.add(objectName); } catch (final Exception e) { // no-op } catch (final NoClassDefFoundError ncdfe) { // no-op } } } private void bindService(final ServiceInfo serviceInfo, final Object service) throws OpenEJBException { try { this.containerSystem.getJNDIContext().bind(JAVA_OPENEJB_NAMING_CONTEXT + serviceInfo.service + "/" + serviceInfo.id, service); } catch (final NamingException e) { throw new OpenEJBException(messages.format("assembler.cannotBindServiceWithId", serviceInfo.service, serviceInfo.id), e); } } public void removeContainer(final String containerId) { containerSystem.removeContainer(containerId); // Update the config tree for (final Iterator iterator = config.containerSystem.containers.iterator(); iterator.hasNext(); ) { final ContainerInfo containerInfo = iterator.next(); if (containerInfo.id.equals(containerId)) { iterator.remove(); try { this.containerSystem.getJNDIContext().unbind(JAVA_OPENEJB_NAMING_CONTEXT + containerInfo.service + "/" + containerInfo.id); } catch (final Exception e) { logger.error("removeContainer.unbindFailed", containerId); } } } } public void createService(final ServiceInfo serviceInfo) throws OpenEJBException { final ObjectRecipe serviceRecipe = createRecipe(serviceInfo); serviceRecipe.setProperty("properties", new UnsetPropertiesRecipe()); final Object service = serviceRecipe.create(); SystemInstance.get().addObserver(service); logUnusedProperties(serviceRecipe, serviceInfo); final Class serviceClass = service.getClass(); getContext().put(serviceClass.getName(), service); props.put(serviceClass.getName(), service); props.put(serviceInfo.service, service); props.put(serviceInfo.id, service); config.facilities.services.add(serviceInfo); logger.getChildLogger("service").debug("createService.success", serviceInfo.service, serviceInfo.id, serviceInfo.className); } public void createProxyFactory(final ProxyFactoryInfo serviceInfo) throws OpenEJBException { final ObjectRecipe serviceRecipe = createRecipe(serviceInfo); final Object service = serviceRecipe.create(); logUnusedProperties(serviceRecipe, serviceInfo); final Class interfce = serviceInterfaces.get(serviceInfo.service); checkImplementation(interfce, service.getClass(), serviceInfo.service, serviceInfo.id); ProxyManager.registerFactory(serviceInfo.id, (ProxyFactory) service); ProxyManager.setDefaultFactory(serviceInfo.id); bindService(serviceInfo, service); setSystemInstanceComponent(interfce, service); getContext().put(interfce.getName(), service); props.put(interfce.getName(), service); props.put(serviceInfo.service, service); props.put(serviceInfo.id, service); // Update the config tree config.facilities.intraVmServer = serviceInfo; logger.getChildLogger("service").debug("createService.success", serviceInfo.service, serviceInfo.id, serviceInfo.className); } private void replaceResourceAdapterProperty(final ObjectRecipe serviceRecipe) throws OpenEJBException { final Object resourceAdapterId = serviceRecipe.getProperty("ResourceAdapter"); if (String.class.isInstance(resourceAdapterId)) { String id = String.class.cast(resourceAdapterId); id = id.trim(); Object resourceAdapter = null; try { resourceAdapter = containerSystem.getJNDIContext().lookup("openejb/Resource/" + id); } catch (final NamingException e) { // handled below } if (resourceAdapter == null) { throw new OpenEJBException("No existing resource adapter defined with id '" + id + "'."); } if (!ResourceAdapter.class.isInstance(resourceAdapter)) { throw new OpenEJBException(messages.format("assembler.resourceAdapterNotResourceAdapter", id, resourceAdapter.getClass())); } serviceRecipe.setProperty("ResourceAdapter", resourceAdapter); } } public void createResource(final ResourceInfo serviceInfo) throws OpenEJBException { final Object service = "true".equalsIgnoreCase(String.valueOf(serviceInfo.properties.remove("Lazy"))) ? newLazyResource(serviceInfo) : doCreateResource(serviceInfo); bindResource(serviceInfo.id, service, false); for (final String alias : serviceInfo.aliases) { bindResource(alias, service, false); } if (serviceInfo.originAppName != null && !serviceInfo.originAppName.isEmpty() && !"/".equals(serviceInfo.originAppName) && !serviceInfo.id.startsWith("global")) { final String baseJndiName = serviceInfo.id.substring(serviceInfo.originAppName.length() + 1); serviceInfo.aliases.add(baseJndiName); final ContextualJndiReference ref = new ContextualJndiReference(baseJndiName); ref.addPrefix(serviceInfo.originAppName); bindResource(baseJndiName, ref, false); } // Update the config tree config.facilities.resources.add(serviceInfo); if (logger.isDebugEnabled()) { // weird to check parent logger but save time and it is almost never activated logger.getChildLogger("service").debug("createService.success", serviceInfo.service, serviceInfo.id, serviceInfo.className); } } private LazyResource newLazyResource(final ResourceInfo serviceInfo) { final ClassLoader loader = Thread.currentThread().getContextClassLoader(); return new LazyResource(new Callable() { @Override public Object call() throws Exception { final boolean appClassLoader = "true".equals(serviceInfo.properties.remove("UseAppClassLoader")); ClassLoader old = null; if (!appClassLoader) { old = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(loader); } try { return doCreateResource(serviceInfo); } finally { if (old != null) { Thread.currentThread().setContextClassLoader(old); } } } }); } private Object doCreateResource(final ResourceInfo serviceInfo) throws OpenEJBException { final ObjectRecipe serviceRecipe = createRecipe(serviceInfo); final boolean properties = PropertiesFactory.class.getName().equals(serviceInfo.className); if ("false".equalsIgnoreCase(serviceInfo.properties.getProperty("SkipImplicitAttributes", "false")) && !properties) { serviceRecipe.setProperty("transactionManager", transactionManager); serviceRecipe.setProperty("ServiceId", serviceInfo.id); } serviceInfo.properties.remove("SkipImplicitAttributes"); final AtomicReference injectedProperties = new AtomicReference(); serviceRecipe.setProperty("properties", new UnsetPropertiesRecipe() { @Override protected Object internalCreate(final Type expectedType, final boolean lazyRefAllowed) throws ConstructionException { final Map original = serviceRecipe.getUnsetProperties(); final Properties properties = new SuperProperties() { @Override public Object remove(final Object key) { // avoid to log them then original.remove(key); return super.remove(key); } }.caseInsensitive(true); // keep our nice case insensitive feature for (final Map.Entry entry : original.entrySet()) { properties.put(entry.getKey(), entry.getValue()); } injectedProperties.set(properties); return properties; } }); final Properties props = PropertyPlaceHolderHelper.simpleHolds(serviceInfo.properties); if (serviceInfo.properties.containsKey("Definition")) { try { // we catch classcast etc..., if it fails it is not important final InputStream is = new ByteArrayInputStream(serviceInfo.properties.getProperty("Definition").getBytes()); final Properties p = new SuperProperties(); IO.readProperties(is, p); for (final Entry entry : p.entrySet()) { final String key = entry.getKey().toString(); if (!props.containsKey(key) // never override from Definition, just use it to complete the properties set && !(key.equalsIgnoreCase("url") && props.containsKey("JdbcUrl"))) { // with @DataSource we can get both, see org.apache.openejb.config.ConvertDataSourceDefinitions.rawDefinition() props.put(key, entry.getValue()); } } } catch (final Exception e) { // ignored } } serviceRecipe.setProperty("Definition", PropertiesHelper.propertiesToString(props)); replaceResourceAdapterProperty(serviceRecipe); ClassLoader loader = Thread.currentThread().getContextClassLoader(); boolean customLoader = false; try { if (serviceInfo.classpath != null && serviceInfo.classpath.length > 0) { final URL[] urls = new URL[serviceInfo.classpath.length]; for (int i = 0; i < serviceInfo.classpath.length; i++) { urls[i] = serviceInfo.classpath[i].toURL(); } loader = new URLClassLoaderFirst(urls, loader); customLoader = true; serviceRecipe.setProperty("OpenEJBResourceClasspath", "true"); } } catch (final MalformedURLException e) { throw new OpenEJBException("Unable to create a classloader for " + serviceInfo.id, e); } Object service = serviceRecipe.create(loader); if (customLoader) { final Collection> apis = new ArrayList>(Arrays.asList(service.getClass().getInterfaces())); if (apis.size() - (apis.contains(Serializable.class) ? 1 : 0) - (apis.contains(Externalizable.class) ? 1 : 0) > 0) { service = Proxy.newProxyInstance(loader, apis.toArray(new Class[apis.size()]), new ClassLoaderAwareHandler(null, service, loader)); } // else proxy would be useless } serviceInfo.unsetProperties = injectedProperties.get(); // Java Connector spec ResourceAdapters and ManagedConnectionFactories need special activation if (ResourceAdapter.class.isInstance(service)) { final ResourceAdapter resourceAdapter = ResourceAdapter.class.cast(service); // Create a thead pool for work manager final int threadPoolSize = getIntProperty(serviceInfo.properties, "threadPoolSize", 30); final Executor threadPool; if (threadPoolSize <= 0) { logger.warning("Thread pool for '" + serviceInfo.id + "' is (unbounded), consider setting a size using: " + serviceInfo.id + ".QueueSize=[size]"); threadPool = Executors.newCachedThreadPool(new DaemonThreadFactory(serviceInfo.id + "-worker-")); } else { threadPool = new ExecutorBuilder() .size(threadPoolSize) .prefix(serviceInfo.id) .threadFactory(new DaemonThreadFactory(serviceInfo.id + "-worker-")) .build(new Options(serviceInfo.properties, SystemInstance.get().getOptions())); logger.info("Thread pool size for '" + serviceInfo.id + "' is (" + threadPoolSize + ")"); } // WorkManager: the resource adapter can use this to dispatch messages or perform tasks final WorkManager workManager; if (GeronimoTransactionManager.class.isInstance(transactionManager)) { final GeronimoTransactionManager geronimoTransactionManager = (GeronimoTransactionManager) transactionManager; final TransactionContextHandler txWorkContextHandler = new TransactionContextHandler(geronimoTransactionManager); // use id as default realm name if realm is not specified in service properties final String securityRealmName = getStringProperty(serviceInfo.properties, "realm", serviceInfo.id); final SecurityContextHandler securityContextHandler = new SecurityContextHandler(securityRealmName); final HintsContextHandler hintsContextHandler = new HintsContextHandler(); final Collection workContextHandlers = new ArrayList(); workContextHandlers.add(txWorkContextHandler); workContextHandlers.add(securityContextHandler); workContextHandlers.add(hintsContextHandler); workManager = new GeronimoWorkManager(threadPool, threadPool, threadPool, workContextHandlers); } else { workManager = new SimpleWorkManager(threadPool); } // BootstrapContext: wraps the WorkMananger and XATerminator final BootstrapContext bootstrapContext; if (GeronimoTransactionManager.class.isInstance(transactionManager)) { bootstrapContext = new GeronimoBootstrapContext(GeronimoWorkManager.class.cast(workManager), (GeronimoTransactionManager) transactionManager, (GeronimoTransactionManager) transactionManager); } else if (XATerminator.class.isInstance(transactionManager)) { bootstrapContext = new SimpleBootstrapContext(workManager, XATerminator.class.cast(transactionManager)); } else { bootstrapContext = new SimpleBootstrapContext(workManager); } // start the resource adapter try { logger.debug("createResource.startingResourceAdapter", serviceInfo.id, service.getClass().getName()); resourceAdapter.start(bootstrapContext); } catch (final ResourceAdapterInternalException e) { throw new OpenEJBException(e); } final Map unset = serviceRecipe.getUnsetProperties(); unset.remove("threadPoolSize"); logUnusedProperties(unset, serviceInfo); service = new ResourceAdapterReference(resourceAdapter, threadPool, OPENEJB_RESOURCE_JNDI_PREFIX + serviceInfo.id); } else if (ManagedConnectionFactory.class.isInstance(service)) { final ManagedConnectionFactory managedConnectionFactory = ManagedConnectionFactory.class.cast(service); // connection manager is constructed via a recipe so we automatically expose all cmf properties final ObjectRecipe connectionManagerRecipe = new ObjectRecipe(GeronimoConnectionManagerFactory.class, "create"); connectionManagerRecipe.allow(Option.CASE_INSENSITIVE_PROPERTIES); connectionManagerRecipe.allow(Option.IGNORE_MISSING_PROPERTIES); connectionManagerRecipe.setAllProperties(serviceInfo.properties); connectionManagerRecipe.setProperty("name", serviceInfo.id); connectionManagerRecipe.setProperty("mcf", managedConnectionFactory); // standard properties connectionManagerRecipe.setProperty("transactionManager", transactionManager); ClassLoader classLoader = loader; if (classLoader == null) { classLoader = getClass().getClassLoader(); } if (classLoader == null) { classLoader = ClassLoader.getSystemClassLoader(); } connectionManagerRecipe.setProperty("classLoader", classLoader); logger.getChildLogger("service").info("createResource.createConnectionManager", serviceInfo.id, service.getClass().getName()); // create the connection manager final ConnectionManager connectionManager = (ConnectionManager) connectionManagerRecipe.create(); if (connectionManager == null) { throw new OpenEJBRuntimeException(messages.format("assembler.invalidConnectionManager", serviceInfo.id)); } final Map unsetA = serviceRecipe.getUnsetProperties(); final Map unsetB = connectionManagerRecipe.getUnsetProperties(); final Map unset = new HashMap(); for (final Entry entry : unsetA.entrySet()) { if (unsetB.containsKey(entry.getKey())) { unset.put(entry.getKey(), entry.getValue()); } } // service becomes a ConnectorReference which merges connection manager and mcf service = new ConnectorReference(connectionManager, managedConnectionFactory); // init cm if needed final Object eagerInit = unset.remove("eagerInit"); if (eagerInit != null && String.class.isInstance(eagerInit) && "true".equalsIgnoreCase(String.class.cast(eagerInit)) && AbstractConnectionManager.class.isInstance(connectionManager)) { try { AbstractConnectionManager.class.cast(connectionManager).doStart(); try { final Object cf = managedConnectionFactory.createConnectionFactory(connectionManager); if (ConnectionFactory.class.isInstance(cf)) { final Connection connection = ConnectionFactory.class.cast(cf).getConnection(); connection.getMetaData(); connection.close(); } } catch (final Exception e) { // no-op: just to force eager init of pool } } catch (final Exception e) { logger.warning("Failed to start connection manager", e); } } logUnusedProperties(unset, serviceInfo); } else if (DataSource.class.isInstance(service)) { ClassLoader classLoader = loader; if (classLoader == null) { classLoader = getClass().getClassLoader(); } final ImportSql importer = new ImportSql(classLoader, serviceInfo.id, DataSource.class.cast(service)); if (importer.hasSomethingToImport()) { importer.doImport(); } final ObjectRecipe recipe = DataSourceFactory.forgetRecipe(service, serviceRecipe); if (recipe != serviceRecipe || !serviceInfo.properties.containsKey("XaDataSource")) { logUnusedProperties(recipe, serviceInfo); } // else logged on xadatasource itself final Properties prop = serviceInfo.properties; String url = prop.getProperty("JdbcUrl", prop.getProperty("url")); if (url == null) { url = prop.getProperty("jdbcUrl"); } if (url == null) { logger.debug("Failed to find url for " + serviceInfo.id + " will not monitor it"); } else { final String host = extractHost(url); if (host != null) { remoteResourceMonitor.addHost(host); remoteResourceMonitor.registerIfNot(); } } } else if (!Properties.class.isInstance(service)) { if (serviceInfo.unsetProperties == null || isTemplatizedResource(serviceInfo)) { logUnusedProperties(serviceRecipe, serviceInfo); } // else wait post construct } return service; } private void bindResource(final String id, final Object service, final boolean canReplace) throws OpenEJBException { final String name = OPENEJB_RESOURCE_JNDI_PREFIX + id; final Context jndiContext = containerSystem.getJNDIContext(); Object existing = null; try { ContextualJndiReference.followReference.set(false); existing = jndiContext.lookup(name); } catch (final Exception ignored) { // no-op } finally { ContextualJndiReference.followReference.remove(); // if the lookup fails the remove is not done } boolean rebind = false; if (existing != null) { final boolean existingIsContextual = ContextualJndiReference.class.isInstance(existing); final boolean serviceIsExisting = ContextualJndiReference.class.isInstance(service); if (!existingIsContextual && serviceIsExisting) { ContextualJndiReference.class.cast(service).setDefaultValue(existing); rebind = true; } else if (existingIsContextual && !serviceIsExisting) { ContextualJndiReference.class.cast(existing).setDefaultValue(service); } else if (existingIsContextual) { // && serviceIsExisting is always true here final ContextualJndiReference contextual = ContextualJndiReference.class.cast(existing); if (canReplace && contextual.prefixesSize() == 1) { // replace! contextual.removePrefix(contextual.lastPrefix()); contextual.setDefaultValue(service); } else { contextual.addPrefix(ContextualJndiReference.class.cast(service).lastPrefix()); } return; } } try { if (canReplace && existing != null) { jndiContext.unbind(name); } if (rebind) { jndiContext.rebind(name, service); } else { jndiContext.bind(name, service); } } catch (final NameAlreadyBoundException nabe) { logger.warning("unbounding resource " + name + " can happen because of a redeployment or because of a duplicated id"); try { jndiContext.unbind(name); jndiContext.bind(name, service); } catch (final NamingException e) { throw new OpenEJBException("Cannot bind resource adapter with id " + id, e); } } catch (final NamingException e) { throw new OpenEJBException("Cannot bind resource adapter with id " + id, e); } } private static String extractHost(final String url) { // can be enhanced if (url == null || !url.contains("://")) { return null; } final int idx = url.indexOf("://"); final String subUrl = url.substring(idx + 3); final int port = subUrl.indexOf(':'); final int slash = subUrl.indexOf('/'); int end = port; if (end < 0 || slash > 0 && slash < end) { end = slash; } if (end > 0) { return subUrl.substring(0, end); } return subUrl; } private int getIntProperty(final Properties properties, final String propertyName, final int defaultValue) { final String propertyValue = getStringProperty(properties, propertyName, Integer.toString(defaultValue)); if (propertyValue == null) { return defaultValue; } try { return Integer.parseInt(propertyValue); } catch (final NumberFormatException e) { throw new IllegalArgumentException(propertyName + " is not an integer " + propertyValue, e); } } private String getStringProperty(final Properties properties, final String propertyName, final String defaultValue) { final String propertyValue = properties.getProperty(propertyName); if (propertyValue == null) { return defaultValue; } return propertyValue; } public void createConnectionManager(final ConnectionManagerInfo serviceInfo) throws OpenEJBException { final ObjectRecipe serviceRecipe = createRecipe(serviceInfo); final Object object = props.get("TransactionManager"); serviceRecipe.setProperty("transactionManager", object); final Object service = serviceRecipe.create(); logUnusedProperties(serviceRecipe, serviceInfo); final Class interfce = serviceInterfaces.get(serviceInfo.service); checkImplementation(interfce, service.getClass(), serviceInfo.service, serviceInfo.id); bindService(serviceInfo, service); setSystemInstanceComponent(interfce, service); getContext().put(interfce.getName(), service); props.put(interfce.getName(), service); props.put(serviceInfo.service, service); props.put(serviceInfo.id, service); // Update the config tree config.facilities.connectionManagers.add(serviceInfo); logger.getChildLogger("service").debug("createService.success", serviceInfo.service, serviceInfo.id, serviceInfo.className); } public void createSecurityService(final SecurityServiceInfo serviceInfo) throws OpenEJBException { Object service = SystemInstance.get().getComponent(SecurityService.class); if (service == null) { final ObjectRecipe serviceRecipe = createRecipe(serviceInfo); service = serviceRecipe.create(); logUnusedProperties(serviceRecipe, serviceInfo); } final Class interfce = serviceInterfaces.get(serviceInfo.service); checkImplementation(interfce, service.getClass(), serviceInfo.service, serviceInfo.id); try { this.containerSystem.getJNDIContext().bind(JAVA_OPENEJB_NAMING_CONTEXT + serviceInfo.service, service); } catch (final NamingException e) { throw new OpenEJBException("Cannot bind " + serviceInfo.service + " with id " + serviceInfo.id, e); } setSystemInstanceComponent(interfce, service); getContext().put(interfce.getName(), service); props.put(interfce.getName(), service); props.put(serviceInfo.service, service); props.put(serviceInfo.id, service); this.securityService = (SecurityService) service; // Update the config tree config.facilities.securityService = serviceInfo; logger.getChildLogger("service").debug("createService.success", serviceInfo.service, serviceInfo.id, serviceInfo.className); } public void createTransactionManager(final TransactionServiceInfo serviceInfo) throws OpenEJBException { Object service = SystemInstance.get().getComponent(TransactionManager.class); if (service == null) { final ObjectRecipe serviceRecipe = createRecipe(serviceInfo); service = serviceRecipe.create(); logUnusedProperties(serviceRecipe, serviceInfo); } else { logger.info("Reusing provided TransactionManager " + service); } final Class interfce = serviceInterfaces.get(serviceInfo.service); checkImplementation(interfce, service.getClass(), serviceInfo.service, serviceInfo.id); try { this.containerSystem.getJNDIContext().bind(JAVA_OPENEJB_NAMING_CONTEXT + serviceInfo.service, service); this.containerSystem.getJNDIContext().bind("comp/UserTransaction", new CoreUserTransaction((TransactionManager) service)); this.containerSystem.getJNDIContext().bind("comp/TransactionManager", service); } catch (final NamingException e) { throw new OpenEJBException("Cannot bind " + serviceInfo.service + " with id " + serviceInfo.id, e); } setSystemInstanceComponent(interfce, service); getContext().put(interfce.getName(), service); props.put(interfce.getName(), service); props.put(serviceInfo.service, service); props.put(serviceInfo.id, service); this.transactionManager = (TransactionManager) service; // Update the config tree config.facilities.transactionService = serviceInfo; // todo find a better place for this // TransactionSynchronizationRegistry final TransactionSynchronizationRegistry synchronizationRegistry; if (TransactionSynchronizationRegistry.class.isInstance(transactionManager)) { synchronizationRegistry = TransactionSynchronizationRegistry.class.cast(transactionManager); } else { // todo this should be built synchronizationRegistry = new SimpleTransactionSynchronizationRegistry(transactionManager); } Assembler.getContext().put(TransactionSynchronizationRegistry.class.getName(), synchronizationRegistry); SystemInstance.get().setComponent(TransactionSynchronizationRegistry.class, synchronizationRegistry); try { this.containerSystem.getJNDIContext().bind("comp/TransactionSynchronizationRegistry", new TransactionSynchronizationRegistryWrapper()); } catch (final NamingException e) { throw new OpenEJBException("Cannot bind java:comp/TransactionSynchronizationRegistry", e); } // JtaEntityManagerRegistry // todo this should be built final JtaEntityManagerRegistry jtaEntityManagerRegistry = new JtaEntityManagerRegistry(synchronizationRegistry); Assembler.getContext().put(JtaEntityManagerRegistry.class.getName(), jtaEntityManagerRegistry); SystemInstance.get().setComponent(JtaEntityManagerRegistry.class, jtaEntityManagerRegistry); logger.getChildLogger("service").debug("createService.success", serviceInfo.service, serviceInfo.id, serviceInfo.className); } public static void logUnusedProperties(final ObjectRecipe serviceRecipe, final ServiceInfo info) { final Map unsetProperties = serviceRecipe.getUnsetProperties(); logUnusedProperties(unsetProperties, info); } private static void logUnusedProperties(final Map unsetProperties, final ServiceInfo info) { Logger logger = null; for (final String property : unsetProperties.keySet()) { //TODO: DMB: Make more robust later if (property.equalsIgnoreCase("Definition")) { return; } if (property.equalsIgnoreCase("SkipImplicitAttributes")) { return; } if (property.equalsIgnoreCase("JndiName")) { return; } if (property.equalsIgnoreCase("Origin")) { return; } if (property.equalsIgnoreCase("DatabaseName")) { return; } if (property.equalsIgnoreCase("connectionAttributes")) { return; } if (property.equalsIgnoreCase("properties")) { return; } if (property.equalsIgnoreCase("ApplicationWide")) { return; } if (property.equalsIgnoreCase("OpenEJBResourceClasspath")) { continue; } if (property.equalsIgnoreCase("transactionManager")) { return; } if (info.types.contains("javax.mail.Session")) { return; } //--- if (info.types.isEmpty() && "class".equalsIgnoreCase(property)) { continue; // inline service (no sp) } if (logger == null) { final Assembler a = SystemInstance.get().getComponent(Assembler.class); if (a != null) { logger = a.logger; } } unusedProperty(info.id, logger, property); } } private static void unusedProperty(final String id, final Logger parentLogger, final String property) { parentLogger.getChildLogger("service").warning("unusedProperty", property, id); } public static ObjectRecipe prepareRecipe(final ServiceInfo info) { final String[] constructorArgs = info.constructorArgs.toArray(new String[info.constructorArgs.size()]); final ObjectRecipe serviceRecipe = new ObjectRecipe(info.className, info.factoryMethod, constructorArgs, null); serviceRecipe.allow(Option.CASE_INSENSITIVE_PROPERTIES); serviceRecipe.allow(Option.IGNORE_MISSING_PROPERTIES); serviceRecipe.allow(Option.PRIVATE_PROPERTIES); return serviceRecipe; } private ObjectRecipe createRecipe(final ServiceInfo info) { final Logger serviceLogger = logger.getChildLogger("service"); if (ResourceInfo.class.isInstance(info)) { final List aliasesList = ResourceInfo.class.cast(info).aliases; if (!aliasesList.isEmpty()) { final String aliases = Join.join(", ", aliasesList); serviceLogger.info("createServiceWithAliases", info.service, info.id, aliases); } else { serviceLogger.info("createService", info.service, info.id); } } else { serviceLogger.info("createService", info.service, info.id); } final ObjectRecipe serviceRecipe = prepareRecipe(info); final Object value = info.properties.remove("SkipImplicitAttributes"); // we don't want this one to go in recipe serviceRecipe.setAllProperties(info.properties); if (value != null) { info.properties.put("SkipImplicitAttributes", value); } if (serviceLogger.isDebugEnabled()) { for (final Map.Entry entry : serviceRecipe.getProperties().entrySet()) { serviceLogger.debug("createService.props", entry.getKey(), entry.getValue()); } } return serviceRecipe; } @SuppressWarnings({"unchecked"}) private void setSystemInstanceComponent(final Class interfce, final Object service) { SystemInstance.get().setComponent(interfce, service); } private URL toUrl(final String jarPath) throws OpenEJBException { try { return new File(jarPath).toURI().toURL(); } catch (final MalformedURLException e) { throw new OpenEJBException(messages.format("cl0001", jarPath, e.getMessage()), e); } } public Logger getLogger() { return logger; } private static class PersistenceClassLoaderHandlerImpl implements PersistenceClassLoaderHandler { private static final AtomicBoolean logged = new AtomicBoolean(false); private final Map> transformers = new TreeMap>(); @Override public void addTransformer(final String unitId, final ClassLoader classLoader, final ClassFileTransformer classFileTransformer) { final Instrumentation instrumentation = Agent.getInstrumentation(); if (instrumentation != null) { instrumentation.addTransformer(classFileTransformer); if (unitId != null) { List transformers = this.transformers.get(unitId); if (transformers == null) { transformers = new ArrayList(1); this.transformers.put(unitId, transformers); } transformers.add(classFileTransformer); } } else if (!logged.getAndSet(true)) { final Assembler a = SystemInstance.get().getComponent(Assembler.class); if (a != null) { a.getLogger().warning("assembler.noAgent"); } } } @Override public void destroy(final String unitId) { final List transformers = this.transformers.remove(unitId); if (transformers != null) { final Instrumentation instrumentation = Agent.getInstrumentation(); if (instrumentation != null) { for (final ClassFileTransformer transformer : transformers) { instrumentation.removeTransformer(transformer); } } else { final Assembler a = SystemInstance.get().getComponent(Assembler.class); if (a != null) { a.getLogger().error("assembler.noAgent"); } } } } @Override public ClassLoader getNewTempClassLoader(final ClassLoader classLoader) { return ClassLoaderUtil.createTempClassLoader(classLoader); } } public static class DeploymentListenerObserver { private final DeploymentListener delegate; public DeploymentListenerObserver(final DeploymentListener deploymentListener) { delegate = deploymentListener; } public void afterApplicationCreated(@Observes final AssemblerAfterApplicationCreated event) { delegate.afterApplicationCreated(event.getApp()); } public void beforeApplicationDestroyed(@Observes final AssemblerBeforeApplicationDestroyed event) { delegate.beforeApplicationDestroyed(event.getApp()); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (!DeploymentListenerObserver.class.isInstance(o)) { return false; } final DeploymentListenerObserver that = DeploymentListenerObserver.class.cast(o); return !(delegate != null ? !delegate.equals(that.delegate) : that.delegate != null); } @Override public int hashCode() { return delegate != null ? delegate.hashCode() : 0; } } private static final class DestroyingResource { private final String name; private final String clazz; private final Object instance; private DestroyingResource(final String name, final String clazz, final Object instance) { this.name = name; this.clazz = clazz; this.instance = instance; } } public static final class ResourceAdapterReference extends Reference { private final transient ResourceAdapter ra; private final transient Executor pool; private final String jndi; public ResourceAdapterReference(final ResourceAdapter ra, final Executor pool, final String jndi) { this.ra = ra; this.pool = pool; this.jndi = jndi; } public Executor getPool() { return pool; } public ResourceAdapter getRa() { return ra; } public String getJndi() { return jndi; } @Override public Object getObject() throws NamingException { return ra; } protected Object readResolve() throws ObjectStreamException { try { final ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class); if (containerSystem != null) { return containerSystem.getJNDIContext().lookup(jndi); } } catch (final Exception e) { throw new InvalidObjectException("name not found: " + jndi); } return null; } } public static class LazyResource extends LazyObjectReference { public LazyResource(final Callable creator) { super(creator); } Object writeReplace() throws ObjectStreamException { try { return getObject(); } catch (final NamingException e) { return null; } } } public static class ResourceInstance extends Reference implements Serializable, DestroyableResource { private final String name; private final Object delegate; private final transient Collection preDestroys; private final transient CreationalContext context; public ResourceInstance(final String name, final Object delegate, final Collection preDestroys, final CreationalContext context) { this.name = name; this.delegate = delegate; this.preDestroys = preDestroys; this.context = context; } @Override public Object getObject() throws NamingException { return delegate; } @Override public void destroyResource() { final Object o = unwrapReference(delegate); for (final Method m : preDestroys) { try { if (!m.isAccessible()) { SetAccessible.on(m); } m.invoke(o); } catch (final Exception e) { final Assembler assembler = SystemInstance.get().getComponent(Assembler.class); if (assembler != null) { assembler.getLogger().error(e.getMessage(), e); } } } try { if (context != null) { context.release(); } } catch (final Exception e) { // no-op } } // we don't care unwrapping the resource here since we want to keep ResourceInstance data for destruction // which is never serialized (IvmContext) Object readResolve() throws ObjectStreamException { try { final ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class); if (containerSystem != null) { return containerSystem.getJNDIContext().lookup(name); } } catch (final NamingException e) { throw new IllegalStateException(e); } return null; } } }