/* * 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.jmx.MBean; import org.apache.openejb.api.resource.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.classic.event.ResourceBeforeDestroyed; import org.apache.openejb.assembler.classic.event.ResourceCreated; import org.apache.openejb.assembler.classic.util.ServiceInfos; import org.apache.openejb.assembler.monitoring.JMXContainer; import org.apache.openejb.async.AsynchronousPool; import org.apache.openejb.batchee.BatchEEServiceManager; import org.apache.openejb.cdi.CdiAppContextsService; import org.apache.openejb.cdi.CdiBuilder; import org.apache.openejb.cdi.CdiPlugin; 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.OpenEJBBeanInfoService; 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.JndiUrlReference; 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.Duration; import org.apache.openejb.util.ExecutorBuilder; import org.apache.openejb.util.JavaSecurityManagers; 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.component.ResourceBean; 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.BeanArchiveService; 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.webbeans.spi.api.ResourceReference; 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.annotation.Resource; import javax.ejb.EJB; 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.enterprise.inject.spi.DefinitionException; import javax.enterprise.inject.spi.DeploymentException; 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.servlet.ServletContext; 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.ByteArrayOutputStream; 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.PrintStream; import java.io.Serializable; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.reflect.Constructor; 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.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeoutException; 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 { static { // avoid linkage error on mac // adding just in case others run into in their tests JULLoggerFactory.class.getName(); } 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"; private static final String GLOBAL_UNIQUE_ID = "global"; public static final String TIMER_STORE_CLASS = "timerStore.class"; private static final ReentrantLock lock = new ReentrantLock(true); 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}; private final boolean skipLoaderIfPossible; Messages messages = new Messages(Assembler.class.getPackage().getName()); public final Logger logger; public final String resourceDestroyTimeout; public final boolean threadStackOnTimeout; private final CoreContainerSystem containerSystem; private final PersistenceClassLoaderHandler persistenceClassLoaderHandler; private final JndiBuilder jndiBuilder; private TransactionManager transactionManager; private SecurityService securityService; protected OpenEjbConfigurationFactory configFactory; private final Map<String, AppInfo> deployedApplications = new HashMap<String, AppInfo>(); private final Map<ObjectName, CreationalContext> creationalContextForAppMbeans = new HashMap<ObjectName, CreationalContext>(); private final Set<ObjectName> containerObjectNames = new HashSet<ObjectName>(); private final RemoteResourceMonitor remoteResourceMonitor = new RemoteResourceMonitor(); @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(); } } 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")); resourceDestroyTimeout = SystemInstance.get().getProperty("openejb.resources.destroy.timeout"); threadStackOnTimeout = "true".equals(SystemInstance.get().getProperty("openejb.resources.destroy.stack-on-timeout", "false")); 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()); initBValFiltering(); } private void initBValFiltering() { if ("true".equals(SystemInstance.get().getProperty("openejb.cdi.bval.filter", "true"))) { try { // bval jars are optional so do it by reflection final ClassLoader loader = ParentClassLoaderFinder.Helper.get(); final Object filter = loader.loadClass("org.apache.openejb.bval.BValCdiFilter").newInstance(); loader.loadClass("org.apache.bval.cdi.BValExtension") .getMethod( "setAnnotatedTypeFilter", loader.loadClass("org.apache.bval.cdi.BValExtension$AnnotatedTypeFilter")) .invoke(null, filter); } catch (final Throwable th) { // ignore, bval not compatible or not present } } } private void installExtensions() { try { final Collection<URL> 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 = JavaSecurityManagers.getSystemProperties(); 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<Map<String, Object>> context = new ThreadLocal<Map<String, Object>>(); public static void setContext(final Map<String, Object> map) { context.set(map); } public static Map<String, Object> getContext() { Map<String, Object> map = context.get(); if (map == null) { map = new HashMap<String, Object>(); context.set(map); } return map; } @Override public void build() throws OpenEJBException { setContext(new HashMap<String, Object>()); 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. * <p/> * This method leverage the other assemble and apply methods which * can be used independently. * <p/> * Assembles and returns the {@link CoreContainerSystem} using the * information from the {@link OpenEjbConfiguration} object passed in. * <pre> * 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 * </pre> * * @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()); } if (hasBatchEE()) { systemInstance.addObserver(new BatchEEServiceManager()); } 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<String> reservedResourceIds = new HashSet<>(configInfo.facilities.resources.size()); for (final AppInfo appInfo : containerSystemInfo.applications) { reservedResourceIds.addAll(appInfo.resourceIds); } final Set<String> rIds = new HashSet<>(configInfo.facilities.resources.size()); for (final ResourceInfo resourceInfo : configInfo.facilities.resources) { createResource(configInfo.facilities.services, resourceInfo); rIds.add(resourceInfo.id); } rIds.removeAll(reservedResourceIds); final ContainerSystem component = systemInstance.getComponent(ContainerSystem.class); if (component != null) { postConstructResources(rIds, ParentClassLoaderFinder.Helper.get(), component.getJNDIContext(), null); }else{ throw new RuntimeException("ContainerSystem has not been initialzed"); } // 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 (exceptionManager != null && e instanceof Exception) { exceptionManager.saveDeploymentException(appInfo, (Exception) e); } } } systemInstance.fireEvent(new ContainerSystemPostCreate()); } private static boolean hasBatchEE() { try { Class.forName("org.apache.batchee.container.services.ServicesManager", true, Assembler.class.getClassLoader()); return true; } catch (final Throwable e) { return false; } } private void createJavaGlobal() { try { containerSystem.getJNDIContext().createSubcontext("global"); } catch (final NamingException e) { // no-op } } public AppInfo getAppInfo(final String path) { return deployedApplications.get(ProvisioningUtil.realLocation(path).iterator().next()); } public boolean isDeployed(final String path) { return deployedApplications.containsKey(ProvisioningUtil.realLocation(path).iterator().next()); } public Collection<AppInfo> getDeployedApplications() { return new ArrayList<AppInfo>(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 { try { mergeServices(appInfo); } catch (final URISyntaxException e) { logger.info("Can't 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 can't handle. final List<String> used = getDuplicates(appInfo); 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); } //Construct the global and app jndi contexts for this app final InjectionBuilder injectionBuilder = new InjectionBuilder(classLoader); final Set<Injection> injections = new HashSet<Injection>(); 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<String, Object> 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<String, Object> appBindings = appBuilder.buildBindings(JndiEncBuilder.JndiScope.app); final Context appJndiContext = appBuilder.build(appBindings); final boolean cdiActive = shouldStartCdi(appInfo); 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<String, LazyValidatorFactory> lazyValidatorFactories = new HashMap<String, LazyValidatorFactory>(); final Map<String, LazyValidator> lazyValidators = new HashMap<String, LazyValidator>(); final boolean isGeronimo = SystemInstance.get().hasProperty("openejb.geronimo"); // try to not create N times the same validator for a single app final Map<ComparableValidationConfig, ValidatorFactory> validatorFactoriesByConfig = new HashMap<ComparableValidationConfig, ValidatorFactory>(); 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<CommonInfoObject> vfs = listCommonInfoObjectsForAppInfo(appInfo); final Map<String, ValidatorFactory> validatorFactories = new HashMap<String, ValidatorFactory>(); for (final CommonInfoObject info : vfs) { if (info.validationInfo == null) { continue; } final ComparableValidationConfig conf = new ComparableValidationConfig( info.validationInfo.providerClassName, info.validationInfo.messageInterpolatorClass, info.validationInfo.traversableResolverClass, info.validationInfo.constraintFactoryClass, info.validationInfo.parameterNameProviderClass, info.validationInfo.version, info.validationInfo.propertyTypes, info.validationInfo.constraintMappings, info.validationInfo.executableValidationEnabled, info.validationInfo.validatedTypes ); 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("can't 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<String, ValidatorFactory> 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<String, String> units = new HashMap<String, String>(); final PersistenceBuilder persistenceBuilder = new PersistenceBuilder(persistenceClassLoaderHandler); for (final PersistenceUnitInfo info : appInfo.persistenceUnits) { final ReloadableEntityManagerFactory factory; try { factory = persistenceBuilder.createEntityManagerFactory(info, classLoader, validatorFactoriesByConfig, cdiActive); 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); // 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(null, connector.resourceAdapter); } for (final ResourceInfo outbound : connector.outbound) { createResource(null, 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(null, adminObject); } } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } } final List<BeanContext> allDeployments = initEjbs(classLoader, appInfo, appContext, injections, new ArrayList<BeanContext>(), null); if ("true".equalsIgnoreCase(SystemInstance.get() .getProperty(PROPAGATE_APPLICATION_EXCEPTIONS, appInfo.properties.getProperty(PROPAGATE_APPLICATION_EXCEPTIONS, "false")))) { propagateApplicationExceptions(appInfo, classLoader, allDeployments); } if (cdiActive) { new CdiBuilder().build(appInfo, appContext, allDeployments); ensureWebBeansContext(appContext); appJndiContext.bind("app/BeanManager", appContext.getBeanManager()); appContext.getBindings().put("app/BeanManager", appContext.getBeanManager()); } else { // ensure we can reuse it in tomcat to remove OWB filters appInfo.properties.setProperty("openejb.cdi.activated", "false"); } // now cdi is started we can try to bind real validator factory and validator if (!isGeronimo) { for (final Entry<String, LazyValidator> 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); } } } startEjbs(start, allDeployments); // App Client for (final ClientInfo clientInfo : appInfo.clients) { // determine the injections final List<Injection> 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); } } // 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); globalEjbResolver.addAll(appInfo.ejbJars); } // bind all global values on global context bindGlobals(appContext.getBindings()); validateCdiResourceProducers(appContext, appInfo); // 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 | DeploymentException 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); } } finally { // cleanup there as well by safety cause we have multiple deployment mode (embedded, tomcat...) for (final WebAppInfo webApp : appInfo.webApps) { appInfo.properties.remove(webApp); } } } private List<String> getDuplicates(final AppInfo appInfo) { final List<String> used = new ArrayList<String>(); for (final EjbJarInfo ejbJarInfo : appInfo.ejbJars) { for (final EnterpriseBeanInfo beanInfo : ejbJarInfo.enterpriseBeans) { if (containerSystem.getBeanContext(beanInfo.ejbDeploymentId) != null) { used.add(beanInfo.ejbDeploymentId); } } } return used; } private boolean shouldStartCdi(final AppInfo appInfo) { if (!"true".equalsIgnoreCase(appInfo.properties.getProperty("openejb.cdi.activated", "true"))) { return false; } for (final EjbJarInfo ejbJarInfo : appInfo.ejbJars) { if (ejbJarInfo.beans != null && (!ejbJarInfo.beans.bdas.isEmpty() || !ejbJarInfo.beans.noDescriptorBdas.isEmpty())) { return true; } } return false; } private void validateCdiResourceProducers(final AppContext appContext, final AppInfo info) { if (appContext.getWebBeansContext() == null) { return; } // validate @Produces @Resource/@PersistenceX/@EJB once all is bound to JNDI - best case - or with our model if (appContext.isStandaloneModule() && !appContext.getProperties().containsKey("openejb.cdi.skip-resource-validation")) { final Map<String, Object> bindings = appContext.getWebContexts().isEmpty() ? appContext.getBindings() : appContext.getWebContexts().iterator().next().getBindings(); if (bindings != null && appContext.getWebBeansContext() != null && appContext.getWebBeansContext().getBeanManagerImpl().isInUse()) { for (final Bean<?> bean : appContext.getWebBeansContext().getBeanManagerImpl().getBeans()) { if (ResourceBean.class.isInstance(bean)) { final ResourceReference reference = ResourceBean.class.cast(bean).getReference(); String jndi = reference.getJndiName().replace("java:", ""); if (reference.getJndiName().startsWith("java:/")) { jndi = jndi.substring(1); } Object lookup = bindings.get(jndi); if (lookup == null && reference.getAnnotation(EJB.class) != null) { final CdiPlugin plugin = CdiPlugin.class.cast(appContext.getWebBeansContext().getPluginLoader().getEjbPlugin()); if (!plugin.isSessionBean(reference.getResourceType())) { // local beans are here and access is O(1) instead of O(n) boolean ok = false; for (final BeanContext bc : appContext.getBeanContexts()) { if (bc.getBusinessLocalInterfaces().contains(reference.getResourceType()) || bc.getBusinessRemoteInterfaces().contains(reference.getResourceType())) { ok = true; break; } } if (!ok) { throw new DefinitionException( "EJB " + reference.getJndiName() + " in " + reference.getOwnerClass() + " can't be cast to " + reference.getResourceType()); } } } if (Reference.class.isInstance(lookup)) { try { lookup = Reference.class.cast(lookup).getContent(); } catch (final Exception e) { // surely too early, let's try some known locations if (JndiUrlReference.class.isInstance(lookup)) { checkBuiltInResourceTypes(reference, JndiUrlReference.class.cast(lookup).getJndiName()); } continue; } } else if (lookup == null) { // TODO: better validation with lookups in tomee, should be in TWAB surely but would split current code final Resource r = Resource.class.cast(reference.getAnnotation(Resource.class)); if (r != null) { if (!r.lookup().isEmpty()) { checkBuiltInResourceTypes(reference, r.lookup()); } else if (!r.name().isEmpty()) { final String name = "comp/env/" + r.name(); boolean done = false; for (final WebAppInfo w : info.webApps) { for (final EnvEntryInfo e : w.jndiEnc.envEntries) { if (name.equals(e.referenceName)) { if (e.type != null && !reference.getResourceType().getName().equals(e.type)) { throw new DefinitionException( "Env Entry " + reference.getJndiName() + " in " + reference.getOwnerClass() + " can't be cast to " + reference.getResourceType()); } done = true; break; } } if (done) { break; } } } } } if (lookup != null && !reference.getResourceType().isInstance(lookup)) { throw new DefinitionException( "Resource " + reference.getJndiName() + " in " + reference.getOwnerClass() + " can't be cast, instance is " + lookup); } } } } } } private void checkBuiltInResourceTypes(final ResourceReference reference, final String jndi) { final Class<?> resourceType = reference.getResourceType(); if ("java:comp/BeanManager".equals(jndi) && resourceType != BeanManager.class) { throw new DefinitionException( "Resource " + reference.getJndiName() + " in " + reference.getOwnerClass() + " can't be cast to a BeanManager"); } else if ("java:comp/TransactionSynchronizationRegistry".equals(jndi) && resourceType != TransactionSynchronizationRegistry.class) { throw new DefinitionException( "Resource " + reference.getJndiName() + " in " + reference.getOwnerClass() + " can't be cast to a TransactionSynchronizationRegistry"); } else if ("java:comp/TransactionManager".equals(jndi) && resourceType != TransactionManager.class) { throw new DefinitionException( "Resource " + reference.getJndiName() + " in " + reference.getOwnerClass() + " can't be cast to a TransactionManager"); } else if ("java:comp/ValidatorFactory".equals(jndi) && resourceType != ValidatorFactory.class) { throw new DefinitionException( "Resource " + reference.getJndiName() + " in " + reference.getOwnerClass() + " can't be cast to a ValidatorFactory"); } else if ("java:comp/Validator".equals(jndi) && resourceType != Validator.class) { throw new DefinitionException( "Resource " + reference.getJndiName() + " in " + reference.getOwnerClass() + " can't be cast to a Validator"); } } private void postConstructResources( final Set<String> 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<ResourceInfo> 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<Method> postConstructs = finder == null ? Collections.<Method>emptyList() : finder.findAnnotatedMethods(PostConstruct.class); final List<Method> preDestroys = finder == null ? Collections.<Method>emptyList() : finder.findAnnotatedMethods(PreDestroy.class); CreationalContext<?> creationalContext = null; Object originalResource = null; if (!postConstructs.isEmpty() || initialize) { originalResource = containerSystemContext.lookup(OPENEJB_RESOURCE_JNDI_PREFIX + resourceInfo.id); Object resource = originalResource; if (resource instanceof Reference) { resource = unwrapReference(resource); this.bindResource(resourceInfo.id, resource, true); } try { // wire up CDI if (appContext != null && appContext.getWebBeansContext() != 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 && !isPassthroughType(resourceInfo)) { final Set<String> 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 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 { // used lazily by JaxWsServiceObjectFactory so merge both to keep same config // note: we could do the same for resources for (final ServiceInfo si : appInfo.services) { if (!appInfo.properties.containsKey(si.id)) { final Map<String, String> 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<CommonInfoObject> listCommonInfoObjectsForAppInfo(final AppInfo appInfo) { final List<CommonInfoObject> vfs = new ArrayList<CommonInfoObject>( 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<String, Object> bindings) throws NamingException { final Context containerSystemContext = containerSystem.getJNDIContext(); for (final Entry<String, Object> 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<BeanContext> 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) { try { // if quartz is missing final Scheduler globalScheduler = SystemInstance.get().getComponent(Scheduler.class); final Collection<Scheduler> schedulers = new ArrayList<Scheduler>(); 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("Can't resume scheduler for " + ejb.getEjbName(), e); } } } catch (final NoClassDefFoundError ncdfe) { // no-op } } public List<BeanContext> initEjbs(final ClassLoader classLoader, final AppInfo appInfo, final AppContext appContext, final Set<Injection> injections, final List<BeanContext> 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) { if (isSkip(appInfo, webappId, ejbJar)) { continue; } final HashMap<String, BeanContext> 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<Map.Entry<Method, MethodContext>> it = beanContext.iteratorMethodContext(); it.hasNext(); ) { final Map.Entry<Method, MethodContext> 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<Map.Entry<Method, MethodContext>> it = beanContext.iteratorMethodContext(); it.hasNext(); ) { final Map.Entry<Method, MethodContext> 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<Entry<Method, MethodContext>> it = beanContext.iteratorMethodContext(); it.hasNext(); ) { final Entry<Method, MethodContext> 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<Class> interfaces = new ArrayList<Class>(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<BeanContext> ejbs = sort(allDeployments); for (final BeanContext b : ejbs) { // otherwise for ears we have duplicated beans if (appContext.getBeanContexts().contains(b)) { continue; } appContext.getBeanContexts().add(b); } return ejbs; } private boolean isSkip(final AppInfo appInfo, final String webappId, final EjbJarInfo ejbJar) { 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 } } return skip; } 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("Can't instantiate " + timerStoreClass + ", using default memory timer store"); } } } return new MemoryTimerStore(EjbTimerServiceImpl.getDefaultTransactionManager()); } public void startEjbs(final boolean start, final List<BeanContext> allDeployments) throws OpenEJBException { // now that everything is configured, deploy to the container if (start) { SystemInstance.get().fireEvent(new BeforeStartEjbs(allDeployments)); final Collection<BeanContext> toStart = new ArrayList<BeanContext>(); // 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<Object, Object> 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<Bean<?>> 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 + " can't be registered because it can't be instantiated", e); return; } catch (final IllegalAccessException e) { logger.error("the mbean " + mbeanClass + " can't be registered because it can't be accessed", e); return; } creationalContext = null; } else { creationalContext = bm.createCreationalContext(bean); instance = bm.getReference(bean, clazz, creationalContext); } final MBeanServer server = LocalMBeanServer.get(); try { final MBean annotation = clazz.getAnnotation(MBean.class); final ObjectName leaf = annotation == null || annotation.objectName().isEmpty() ? new ObjectNameBuilder("openejb.user.mbeans") .set("application", id) .set("group", clazz.getPackage().getName()) .set("name", clazz.getSimpleName()) .build() : new ObjectName(annotation.objectName()); 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 + " can't 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<Class<?>, Object> services = new HashMap<Class<?>, Object>(); services.put(JNDIService.class, new OpenEJBJndiService()); services.put(AppContext.class, appContext); services.put(ScannerService.class, new CdiScanner()); services.put(BeanArchiveService.class, new OpenEJBBeanInfoService()); services.put(ELAdaptor.class, new CustomELAdapter(appContext)); services.put(LoaderService.class, new OptimizedLoaderService(appContext.getProperties())); final Properties properties = new Properties(); properties.setProperty(org.apache.webbeans.spi.SecurityService.class.getName(), ManagedSecurityService.class.getName()); properties.setProperty(ContextsService.class.getName(), CdiAppContextsService.class.getName()); properties.setProperty(ResourceInjectionService.class.getName(), CdiResourceInjectionService.class.getName()); properties.setProperty(TransactionService.class.getName(), OpenEJBTransactionService.class.getName()); webBeansContext = new WebBeansContext(services, properties); appContext.setCdiEnabled(false); 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 (value instanceof TransactionPolicyFactory) { factory = (TransactionPolicyFactory) value; } else if (value instanceof String) { try { final String[] parts = ((String) value).split(":", 2); final ResourceFinder finder = new ResourceFinder("META-INF", classLoader); final Map<String, Class<? extends TransactionPolicyFactory>> plugins = finder.mapAvailableImplementations(TransactionPolicyFactory.class); final Class<? extends TransactionPolicyFactory> 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<BeanContext> sort(List<BeanContext> 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<BeanContext>() { @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<BeanContext>() { @Override public String getName(final BeanContext t) { return (String) t.getDeploymentID(); } @Override public Set<String> 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<BeanContext>() { @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 { final SystemInstance systemInstance = SystemInstance.get(); systemInstance.fireEvent(new ContainerSystemPreDestroy()); try { EjbTimerServiceImpl.shutdown(); } catch (final Exception e) { logger.warning("Unable to shutdown scheduler", e); } catch (final NoClassDefFoundError ncdfe) { // no-op } logger.debug("Undeploying Applications"); final Assembler assembler = this; final List<AppInfo> deployedApps = new ArrayList<AppInfo>(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<ObjectName> 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<Binding> namingEnumeration = null; try { namingEnumeration = containerSystem.getJNDIContext().listBindings("openejb/Resource"); } catch (final NamingException ignored) { // no resource adapters were created } destroyResourceTree("", namingEnumeration); try { containerSystem.getJNDIContext().unbind("java:global"); } catch (final NamingException ignored) { // no-op } systemInstance.removeComponent(OpenEjbConfiguration.class); systemInstance.removeComponent(JtaEntityManagerRegistry.class); systemInstance.removeComponent(TransactionSynchronizationRegistry.class); systemInstance.removeComponent(EjbResolver.class); systemInstance.fireEvent(new AssemblerDestroyed()); systemInstance.removeObservers(); if (DestroyableResource.class.isInstance(this.securityService)) { DestroyableResource.class.cast(this.securityService).destroyResource(); } if (DestroyableResource.class.isInstance(this.transactionManager)) { DestroyableResource.class.cast(this.transactionManager).destroyResource(); } for (final Container c : this.containerSystem.containers()) { if (DestroyableResource.class.isInstance(c)) { // TODO: should we use auto closeable there? DestroyableResource.class.cast(c).destroyResource(); } } SystemInstance.reset(); } finally { l.unlock(); } } private Collection<DestroyingResource> destroyResourceTree(final String base, final NamingEnumeration<Binding> namingEnumeration) { final List<DestroyingResource> resources = new LinkedList<>(); while (namingEnumeration != null && namingEnumeration.hasMoreElements()) { final Binding binding = namingEnumeration.nextElement(); final Object object = binding.getObject(); if (Context.class.isInstance(object)) { try { resources.addAll(destroyResourceTree( IvmContext.class.isInstance(object) ? IvmContext.class.cast(object).mynode.getAtomicName() : "", Context.class.cast(object).listBindings(""))); } catch (final Exception ignored) { // no-op } } else { resources.add(new DestroyingResource((base == null || base.isEmpty() ? "" : (base + '/')) + binding.getName(), binding.getClassName(), object)); } } Collections.sort(resources, new Comparator<DestroyingResource>() { // end by destroying RA after having closed CF pool (for jms for instance) @Override public int compare(final DestroyingResource o1, final DestroyingResource o2) { final boolean ra1 = isRa(o1.instance); final boolean ra2 = isRa(o2.instance); if (ra2 && !ra1) { return -1; } if (ra1 && !ra2) { return 1; } // TODO: handle dependencies there too return o1.name.compareTo(o2.name); } private boolean isRa(final Object instance) { return ResourceAdapter.class.isInstance(instance) || ResourceAdapterReference.class.isInstance(instance); } }); 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 inObject) { Object object; try { object = LazyResource.class.isInstance(inObject) && LazyResource.class.cast(inObject).isInitialized() ? LazyResource.class.cast(inObject).getObject() : inObject; } catch (final NamingException e) { object = inObject; // in case it impl DestroyableResource } final Collection<Method> preDestroy = null; if (resourceDestroyTimeout != null) { final Duration d = new Duration(resourceDestroyTimeout); final ExecutorService es = Executors.newSingleThreadExecutor(new DaemonThreadFactory("openejb-resource-destruction-" + name)); final Object o = object; try { es.submit(new Runnable() { @Override public void run() { doResourceDestruction(name, className, o); } }).get(d.getTime(), d.getUnit()); } catch (final InterruptedException e) { Thread.interrupted(); } catch (final ExecutionException e) { throw RuntimeException.class.cast(e.getCause()); } catch (final TimeoutException e) { logger.error("Can't destroy " + name + " in " + resourceDestroyTimeout + ", giving up.", e); if (threadStackOnTimeout) { final ThreadInfo[] dump = ManagementFactory.getThreadMXBean().dumpAllThreads(false, false); final ByteArrayOutputStream writer = new ByteArrayOutputStream(); final PrintStream stream = new PrintStream(writer); for (final ThreadInfo info : dump) { stream.println('"' + info.getThreadName() + "\" suspended=" + info.isSuspended() + " state=" + info.getThreadState()); for (final StackTraceElement traceElement : info.getStackTrace()) { stream.println("\tat " + traceElement); } } logger.info("Dump on " + name + " destruction timeout:\n" + new String(writer.toByteArray())); } } } else { doResourceDestruction(name, className, object); } removeResourceInfo(name); } private void doResourceDestruction(final String name, final String className, final Object jndiObject) { final ResourceBeforeDestroyed event = new ResourceBeforeDestroyed(jndiObject, name); SystemInstance.get().fireEvent(event); final Object object = event.getReplacement() == null ? jndiObject : event.getReplacement(); if (object instanceof ResourceAdapterReference) { final ResourceAdapterReference resourceAdapter = (ResourceAdapterReference) object; try { logger.info("Stopping ResourceAdapter: " + name); if (logger.isDebugEnabled()) { logger.debug("Stopping ResourceAdapter: " + className); } if (resourceAdapter.pool != null && ExecutorService.class.isInstance(resourceAdapter.pool)) { ExecutorService.class.cast(resourceAdapter.pool).shutdownNow(); } resourceAdapter.ra.stop(); } catch (final Throwable t) { logger.fatal("ResourceAdapter Shutdown Failed: " + name, t); } } else if (object instanceof ResourceAdapter) { final ResourceAdapter resourceAdapter = (ResourceAdapter) 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)) { logger.info("Closing DataSource: " + name); try { DataSourceFactory.destroy(object); } catch (final Throwable t) { //Ignore } } else if (object instanceof ConnectorReference) { final ConnectorReference cr = (ConnectorReference) object; try { final ConnectionManager cm = cr.getConnectionManager(); if (cm != null && cm instanceof AbstractConnectionManager) { ((AbstractConnectionManager) 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); } } public void removeResourceInfo(final String name) { try { //Ensure ResourceInfo for this resource is removed final OpenEjbConfiguration configuration = SystemInstance.get().getComponent(OpenEjbConfiguration.class); final Iterator<ResourceInfo> iterator; if (configuration != null) { iterator = configuration.facilities.resources.iterator(); }else{ throw new Exception("OpenEjbConfiguration has not been initialized"); } 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 Context globalContext = containerSystem.getJNDIContext(); final AppContext appContext = containerSystem.getAppContext(appInfo.appId); final ClassLoader classLoader = appContext.getClassLoader(); SystemInstance.get().fireEvent(new AssemblerBeforeApplicationDestroyed(appInfo, appContext)); //noinspection ConstantConditions if (null == appContext) { logger.warning("Application id '" + appInfo.appId + "' not found in: " + Arrays.toString(containerSystem.getAppContextKeys())); return; } else { final WebBeansContext webBeansContext = appContext.getWebBeansContext(); if (webBeansContext != null) { final ClassLoader old = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); try { final ServletContext context = appContext.isStandaloneModule() && appContext.getWebContexts().iterator().hasNext() ? appContext.getWebContexts().iterator().next().getServletContext() : null; webBeansContext.getService(ContainerLifecycle.class).stopApplication(context); } finally { Thread.currentThread().setContextClassLoader(old); } } final Map<String, Object> cb = appContext.getBindings(); for (final Entry<String, Object> 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<BeanContext> deployments = new ArrayList<BeanContext>(); 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 // Theoreticlly 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 deployment : deployments) { final String deploymentID = String.valueOf(deployment.getDeploymentID()); try { final Container container = deployment.getContainer(); container.stop(deployment); } 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<String> clientIds = new ArrayList<String>(); 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); } 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 (globalContext instanceof IvmContext) { final IvmContext ivmContext = (IvmContext) 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("can't unregister " + objectName + " because the mbean was not found", e); } catch (final MBeanRegistrationException e) { logger.warning("can't unregister " + objectName, e); } catch (final MalformedObjectNameException mone) { logger.warning("can't 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 (object instanceof ContextualJndiReference) { 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("can't 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("can't 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("can't 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 } } } for (final String id : appInfo.containerIds) { removeContainer(id); } 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; } logger.debug("destroyApplication.success", appInfo.path); } finally { l.unlock(); } } private void destroyLookedUpResource(final Context globalContext, final String id, final String name) throws NamingException { final Object object; try { object = globalContext.lookup(name); } catch (final NamingException e) { // if we catch a NamingException, check to see if the resource is a LaztObjectReference that has not been initialized correctly final String ctx = name.substring(0, name.lastIndexOf("/")); final String objName = name.substring(ctx.length() + 1); final NamingEnumeration<Binding> 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(name); return; } } throw e; } 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 { if ("openejb".equals(appInfo.appId)) { return ParentClassLoaderFinder.Helper.get(); } final Set<URL> 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("Unable to find the open-jpa-integration jar"); } } final ClassLoaderEnricher component = SystemInstance.get().getComponent(ClassLoaderEnricher.class); if (component != null) { jars.addAll(Arrays.asList(component.applicationEnrichment())); }else { logger.warning("Unable to find open-jpa-integration jar"); } // 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<File> 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("Can't 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(Collections.<ServiceInfo>emptyList(), 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(); serviceRecipe.getUnsetProperties().remove("id"); // we forced it serviceRecipe.getUnsetProperties().remove("securityService"); // we forced it 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 | NoClassDefFoundError e) { // 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<ContainerInfo> 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(Collections.<ServiceInfo>emptyList(), 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(Collections.<ServiceInfo>emptyList(), 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 (resourceAdapterId instanceof String) { String id = (String) resourceAdapterId; id = id.trim(); Object resourceAdapter = null; try { resourceAdapter = containerSystem.getJNDIContext().lookup("openejb/Resource/" + id); } catch (final NamingException e) { // handled below } if (Reference.class.isInstance(resourceAdapter)) { try { resourceAdapter = Reference.class.cast(resourceAdapter).getContent(); } catch (final NamingException e) { // no-op: will fail after } } if (resourceAdapter == null) { throw new OpenEJBException("No existing resource adapter defined with id '" + id + "'."); } if (!(resourceAdapter instanceof ResourceAdapter)) { throw new OpenEJBException(messages.format("assembler.resourceAdapterNotResourceAdapter", id, resourceAdapter.getClass())); } serviceRecipe.setProperty("ResourceAdapter", resourceAdapter); } } @Deprecated public void createResource(final ResourceInfo serviceInfo) throws OpenEJBException { createResource(null, serviceInfo); } public void createResource(final Collection<ServiceInfo> infos, final ResourceInfo serviceInfo) throws OpenEJBException { final boolean usesCdiPwdCipher = usesCdiPwdCipher(serviceInfo); final Object service = "true".equalsIgnoreCase(String.valueOf(serviceInfo.properties.remove("Lazy"))) || usesCdiPwdCipher ? newLazyResource(infos, serviceInfo) : doCreateResource(infos, serviceInfo); if (usesCdiPwdCipher && !serviceInfo.properties.contains("InitializeAfterDeployment")) { serviceInfo.properties.put("InitializeAfterDeployment", "true"); } 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 boolean usesCdiPwdCipher(final ResourceInfo serviceInfo) { for (final Object val : serviceInfo.properties.values()) { if (String.valueOf(val).startsWith("cipher:cdi:")) { return true; } } return false; } private LazyResource newLazyResource(final Collection<ServiceInfo> infos, final ResourceInfo serviceInfo) { return new LazyResource(new Callable<Object>() { @Override public Object call() throws Exception { final boolean appClassLoader = "true".equals(serviceInfo.properties.remove("UseAppClassLoader")) || serviceInfo.originAppName != null; final Thread thread = Thread.currentThread(); final ClassLoader old = thread.getContextClassLoader(); if (!appClassLoader) { final ClassLoader classLoader = Assembler.class.getClassLoader(); thread.setContextClassLoader(classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader); } // else contextually we should have the app loader try { return doCreateResource(infos, serviceInfo); } finally { thread.setContextClassLoader(old); } } }); } private Object doCreateResource(final Collection<ServiceInfo> infos, final ResourceInfo serviceInfo) throws OpenEJBException { final String skipPropertiesFallback = (String) serviceInfo.properties.remove("SkipPropertiesFallback"); // do it early otherwise we can loose it final ObjectRecipe serviceRecipe = createRecipe(infos, 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"); // if custom instance allow to skip properties fallback to avoid to set unexpectedly it - connectionProps of DBs final AtomicReference<Properties> injectedProperties = new AtomicReference<>(); if (!"true".equalsIgnoreCase(skipPropertiesFallback)) { serviceRecipe.setProperty("properties", new UnsetPropertiesRecipe() { @Override protected Object internalCreate(final Type expectedType, final boolean lazyRefAllowed) throws ConstructionException { final Map<String, Object> 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<String, Object> entry : original.entrySet()) { properties.put(entry.getKey(), entry.getValue()); } injectedProperties.set(properties); return properties; } }); } else { // this is not the best fallback we have but since it is super limited it is acceptable final Map<String, Object> unsetProperties = serviceRecipe.getUnsetProperties(); injectedProperties.set(new Properties() { @Override public String getProperty(final String key) { final Object obj = unsetProperties.get(key); return String.class.isInstance(obj) ? String.valueOf(obj) : null; } @Override public Set<String> stringPropertyNames() { return unsetProperties.keySet(); } @Override public Set<Object> keySet() { //noinspection unchecked return Set.class.cast(unsetProperties.keySet()); } @Override public synchronized boolean containsKey(final Object key) { return getProperty(String.valueOf(key)) != null; } }); } if (serviceInfo.types.contains("DataSource") || serviceInfo.types.contains(DataSource.class.getName())) { final Properties props = PropertyPlaceHolderHelper.simpleHolds(serviceInfo.properties); if (serviceInfo.properties.containsKey("Definition")) { final Object encoding = serviceInfo.properties.remove("DefinitionEncoding"); try { // we catch classcast etc..., if it fails it is not important final InputStream is = new ByteArrayInputStream(serviceInfo.properties.getProperty("Definition") .getBytes(encoding != null ? encoding.toString() : "ISO-8859-1")); final Properties p = new SuperProperties(); IO.readProperties(is, p); for (final Entry<Object, Object> 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)); } // else: any other kind of resource relying on it? shouldnt be 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); } if (!customLoader && serviceInfo.classpathAPI != null) { throw new IllegalArgumentException("custom-api provided but not classpath used for " + serviceInfo.id); } Object service = serviceRecipe.create(loader); if (customLoader) { final Collection<Class<?>> apis; if (serviceInfo.classpathAPI == null) { apis = new ArrayList<Class<?>>(Arrays.asList(service.getClass().getInterfaces())); } else { final String[] split = serviceInfo.classpathAPI.split(" *, *"); apis = new ArrayList<>(split.length); final ClassLoader apiLoader = Thread.currentThread().getContextClassLoader(); for (final String fqn : split) { try { apis.add(apiLoader.loadClass(fqn)); } catch (final ClassNotFoundException e) { throw new IllegalArgumentException(fqn + " not usable as API for " + serviceInfo.id, e); } } } 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 (service instanceof ResourceAdapter) { final ResourceAdapter resourceAdapter = (ResourceAdapter) 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<WorkContextHandler> workContextHandlers = new ArrayList<WorkContextHandler>(); 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 (transactionManager instanceof GeronimoTransactionManager) { bootstrapContext = new GeronimoBootstrapContext(GeronimoWorkManager.class.cast(workManager), (GeronimoTransactionManager) transactionManager, (GeronimoTransactionManager) transactionManager); } else if (transactionManager instanceof XATerminator) { bootstrapContext = new SimpleBootstrapContext(workManager, (XATerminator) 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<String, Object> unset = serviceRecipe.getUnsetProperties(); unset.remove("threadPoolSize"); logUnusedProperties(unset, serviceInfo); service = new ResourceAdapterReference(resourceAdapter, threadPool, OPENEJB_RESOURCE_JNDI_PREFIX + serviceInfo.id); } else if (service instanceof ManagedConnectionFactory) { final ManagedConnectionFactory managedConnectionFactory = (ManagedConnectionFactory) 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<String, Object> unsetA = serviceRecipe.getUnsetProperties(); final Map<String, Object> unsetB = connectionManagerRecipe.getUnsetProperties(); final Map<String, Object> unset = new HashMap<String, Object>(); for (final Entry<String, Object> 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 && eagerInit instanceof String && "true".equalsIgnoreCase((String) eagerInit) && connectionManager instanceof AbstractConnectionManager) { try { ((AbstractConnectionManager) connectionManager).doStart(); try { final Object cf = managedConnectionFactory.createConnectionFactory(connectionManager); if (cf instanceof ConnectionFactory) { final Connection connection = ((ConnectionFactory) 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("Can't start connection manager", e); } } logUnusedProperties(unset, serviceInfo); } else if (service instanceof DataSource) { ClassLoader classLoader = loader; if (classLoader == null) { classLoader = getClass().getClassLoader(); } final ImportSql importer = new ImportSql(classLoader, serviceInfo.id, (DataSource) 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("Unable 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 } final ResourceCreated event = new ResourceCreated(service, serviceInfo.id); SystemInstance.get().fireEvent(event); return event.getReplacement() == null ? service : event.getReplacement(); } 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(Collections.<ServiceInfo>emptyList(), 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(Collections.<ServiceInfo>emptyList(), 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(Collections.<ServiceInfo>emptyList(), 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 (transactionManager instanceof TransactionSynchronizationRegistry) { synchronizationRegistry = (TransactionSynchronizationRegistry) 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<String, Object> unsetProperties = serviceRecipe.getUnsetProperties(); logUnusedProperties(unsetProperties, info); } private static void logUnusedProperties(final Map<String, ?> unsetProperties, final ServiceInfo info) { if (isPassthroughType(info)) { return; } final boolean ignoreJdbcDefault = "Annotation".equalsIgnoreCase(info.properties.getProperty("Origin")); Logger logger = null; for (final String property : unsetProperties.keySet()) { //TODO: DMB: Make more robust later if (ignoreJdbcDefault && ("JdbcUrl".equals(property) || "UserName".equals(property) || "Password".equals(property) || "PasswordCipher".equals(property))) { continue; } if (property.equalsIgnoreCase("Definition")) { continue; } if (property.equalsIgnoreCase("SkipImplicitAttributes")) { continue; } if (property.equalsIgnoreCase("JndiName")) { continue; } if (property.equalsIgnoreCase("Origin")) { continue; } if (property.equalsIgnoreCase("DatabaseName")) { continue; } if (property.equalsIgnoreCase("connectionAttributes")) { return; } if (property.equalsIgnoreCase("properties")) { return; } if (property.equalsIgnoreCase("ApplicationWide")) { continue; } if (property.equalsIgnoreCase("OpenEJBResourceClasspath")) { continue; } if (isInternalProperty(property)) { continue; } if (info.types.isEmpty() && "class".equalsIgnoreCase(property)) { continue; // inline service (no sp) } if ("destination".equalsIgnoreCase(property) && info.id.equals(unsetProperties.get("destination"))) { continue; } if (logger == null) { final Assembler assembler = SystemInstance.get().getComponent(Assembler.class); if (assembler != null) { logger = assembler.logger; }else { System.err.println("Assembler has not been initialized"); } } unusedProperty(info.id, logger, property); } } private static boolean isPassthroughType(final ServiceInfo info) { return info.types.contains("javax.mail.Session"); } private static void unusedProperty(final String id, final Logger parentLogger, final String property) { if (isInternalProperty(property)) { return; } final String msg = "unused property '" + property + "' for resource '" + id + "'"; if (null != parentLogger) { parentLogger.getChildLogger("service").warning(msg); } else { // note: we should throw an exception if this is called, shouldnt be possible in our lifecycle System.out.println(msg); } } private static boolean isInternalProperty(final String property) { return property.equalsIgnoreCase("ServiceId") || property.equalsIgnoreCase("transactionManager"); } private static void unusedProperty(final String id, final String property) { final Assembler component = SystemInstance.get().getComponent(Assembler.class); final Logger logger = component != null ? component.logger : null; unusedProperty(id, logger, property); } 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 Collection<ServiceInfo> services, final ServiceInfo info) { final Logger serviceLogger = logger.getChildLogger("service"); if (info instanceof ResourceInfo) { final List<String> aliasesList = ((ResourceInfo) 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 final Properties allProperties = PropertyPlaceHolderHelper.simpleHolds(info.properties); allProperties.remove("SkipPropertiesFallback"); if (services == null) { // small optim for internal resources serviceRecipe.setAllProperties(allProperties); } else { info.properties = allProperties; ServiceInfos.setProperties(services, info, serviceRecipe); } if (value != null) { info.properties.put("SkipImplicitAttributes", value); } if (serviceLogger.isDebugEnabled()) { for (final Map.Entry<String, Object> 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); } } private static class PersistenceClassLoaderHandlerImpl implements PersistenceClassLoaderHandler { private static final AtomicBoolean logged = new AtomicBoolean(false); private final Map<String, List<ClassFileTransformer>> transformers = new TreeMap<String, List<ClassFileTransformer>>(); @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<ClassFileTransformer> transformers = this.transformers.get(unitId); if (transformers == null) { transformers = new ArrayList<ClassFileTransformer>(1); this.transformers.put(unitId, transformers); } transformers.add(classFileTransformer); } } else if (!logged.getAndSet(true)) { final Assembler assembler = SystemInstance.get().getComponent(Assembler.class); if (assembler != null) { assembler.logger.info("assembler.noAgent"); } else { System.err.println("addTransformer: Assembler not initialized: JAVA AGENT NOT INSTALLED"); } } } @Override public void destroy(final String unitId) { final List<ClassFileTransformer> 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 assembler = SystemInstance.get().getComponent(Assembler.class); if (assembler != null) { assembler.logger.info("assembler.noAgent"); }else { System.err.println("destroy: Assembler not initialized: JAVA AGENT NOT INSTALLED"); } } } } @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 (!(o instanceof DeploymentListenerObserver)) { return false; } final DeploymentListenerObserver that = (DeploymentListenerObserver) 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 component = SystemInstance.get().getComponent(ContainerSystem.class); if (component != null) { return component.getJNDIContext().lookup(jndi); } else { throw new NamingException("ContainerSystem has not been initialized"); } } catch (final NamingException e) { final InvalidObjectException objectException = new InvalidObjectException("name not found: " + jndi); objectException.initCause(e); throw objectException; } } } public static class LazyResource extends LazyObjectReference<Object> { public LazyResource(final Callable<Object> 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<Method> preDestroys; private final transient CreationalContext<?> context; private volatile boolean destroyed = false; public ResourceInstance(final String name, final Object delegate, final Collection<Method> 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 synchronized void destroyResource() { if (destroyed) { return; } 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 component = SystemInstance.get().getComponent(Assembler.class); if (component != null) { component.logger.error(e.getMessage(), e); }else { System.err.println("" + e.getMessage()); } } } try { if (context != null) { context.release(); } } catch (final Exception e) { // no-op } destroyed = true; } // 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 component = SystemInstance.get().getComponent(ContainerSystem.class); if (component != null) { return component.getJNDIContext().lookup(name); }else { throw new Exception("ContainerSystem is not initialized"); } } catch (final Exception e) { throw new IllegalStateException(e); } } } }