/* * 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; import org.apache.openejb.assembler.classic.AppInfo; import org.apache.openejb.assembler.classic.Assembler; import org.apache.openejb.config.AppModule; import org.apache.openejb.config.ConfigurationFactory; import org.apache.openejb.config.ConnectorModule; import org.apache.openejb.config.DeploymentLoader; import org.apache.openejb.config.EjbModule; import org.apache.openejb.config.NewLoaderLogic; import org.apache.openejb.config.PersistenceModule; import org.apache.openejb.config.ValidationFailedException; import org.apache.openejb.core.ParentClassLoaderFinder; import org.apache.openejb.core.ProvidedClassLoaderFinder; import org.apache.openejb.jee.Application; import org.apache.openejb.jee.Beans; import org.apache.openejb.jee.Connector; import org.apache.openejb.jee.EjbJar; import org.apache.openejb.jee.EnterpriseBean; import org.apache.openejb.jee.ManagedBean; import org.apache.openejb.jee.TransactionType; import org.apache.openejb.jee.jpa.unit.Persistence; import org.apache.openejb.jee.jpa.unit.PersistenceUnit; import org.apache.openejb.jee.oejb3.EjbDeployment; import org.apache.openejb.jee.oejb3.OpenejbJar; import org.apache.openejb.loader.Options; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.util.Exceptions; import org.apache.openejb.util.JavaSecurityManagers; import org.apache.openejb.util.Join; import org.apache.openejb.util.JuliLogStreamFactory; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.openejb.util.OptionsLog; import org.apache.openejb.util.ServiceManagerProxy; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.web.lifecycle.test.MockHttpSession; import org.apache.webbeans.web.lifecycle.test.MockServletContext; import org.apache.xbean.naming.context.ContextFlyweight; import javax.ejb.EJBException; import javax.ejb.embeddable.EJBContainer; import javax.ejb.spi.EJBContainerProvider; import javax.naming.Context; import javax.naming.Name; import javax.naming.NameNotFoundException; import javax.naming.NamingException; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.validation.ValidationException; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.logging.LogManager; import static org.apache.openejb.cdi.ScopeHelper.startContexts; import static org.apache.openejb.cdi.ScopeHelper.stopContexts; /** * @version $Rev$ $Date$ */ public final class OpenEjbContainer extends EJBContainer { static { // if tomee embedded was ran we'll lost log otherwise final String logManger = JavaSecurityManagers.getSystemProperty("java.util.logging.manager"); if (logManger != null) { try { Thread.currentThread().getContextClassLoader().loadClass(logManger); } catch (final Exception ignored) { final Field field; try { field = LogManager.class.getDeclaredField("manager"); field.setAccessible(true); field.set(null, new JuliLogStreamFactory.OpenEJBLogManager()); } catch (final Exception ignore) { // ignore } } } } public static final String OPENEJB_EMBEDDED_REMOTABLE = "openejb.embedded.remotable"; public static final String OPENEJB_EJBCONTAINER_CLOSE = "openejb.ejbcontainer.close"; public static final String OPENEJB_EJBCONTAINER_CLOSE_SINGLE = "single-jvm"; private static OpenEjbContainer instance; private static Logger logger; // initialized lazily to get the logging config from properties private ServiceManagerProxy serviceManager; private final Options options; private final OpenEjbContainer.GlobalContext globalJndiContext; private final WebBeansContext webBeanContext; private volatile ServletContext servletContext; private volatile HttpSession session; private OpenEjbContainer(final Map<?, ?> map, final AppContext appContext) { webBeanContext = appContext.getWebBeansContext(); globalJndiContext = new GlobalContext(appContext.getGlobalJndiContext()); final Properties properties = new Properties(); properties.putAll(map); options = new Options(properties); startNetworkServices(); if (webBeanContext != null) { servletContext = new MockServletContext(); session = new MockHttpSession(); try { startContexts(webBeanContext.getContextsService(), servletContext, session); } catch (final Exception e) { logger().warning("can't start all CDI contexts", e); } } } @Override public void close() { if (isSingleClose()) { return; } doClose(); } private static boolean isSingleClose() { return OPENEJB_EJBCONTAINER_CLOSE_SINGLE.equals(SystemInstance.get().getProperty(OPENEJB_EJBCONTAINER_CLOSE, "by-invocation")); } private synchronized void doClose() { if (instance == null) { return; } if (serviceManager != null) { serviceManager.stop(); } try { globalJndiContext.close(); } catch (final NamingException e) { throw new IllegalStateException(e); } final Assembler assembler = SystemInstance.get().getComponent(Assembler.class); if (assembler != null) { for (final AppInfo info : assembler.getDeployedApplications()) { try { assembler.destroyApplication(info); } catch (final UndeployException e) { logger().error(e.getMessage(), e); } } } if (webBeanContext != null) { try { stopContexts(webBeanContext.getContextsService(), servletContext, session); } catch (final Exception e) { logger().warning("can't stop all CDI contexts", e); } } logger().info("Destroying OpenEJB container"); OpenEJB.destroy(); instance = null; } @Override public Context getContext() { return globalJndiContext; } private void startNetworkServices() { if (!options.get(OPENEJB_EMBEDDED_REMOTABLE, false)) { return; } try { serviceManager = new ServiceManagerProxy(); serviceManager.start(); } catch (final ServiceManagerProxy.AlreadyStartedException e) { logger().debug("Network services already started. Ignoring option " + OPENEJB_EMBEDDED_REMOTABLE); } } private static Logger logger() { // don't trigger init too eagerly to be sure to be configured if (logger == null) { logger = Logger.getInstance(LogCategory.OPENEJB_STARTUP, OpenEjbContainer.class); } return logger; } public static class Provider implements EJBContainerProvider { public static final String OPENEJB_ADDITIONNAL_CALLERS_KEY = "openejb.additionnal.callers"; @Override public EJBContainer createEJBContainer(Map<?, ?> map) { if (map == null) { // JBoss EJB API pass null when calling EJBContainer.createEJBContainer() map = new HashMap<Object, Object>(); } if (isOtherProvider(map)) { return null; } if (instance != null || OpenEJB.isInitialized()) { if (!isSingleClose()) { logger().info("EJBContainer already initialized. Call ejbContainer.close() to allow reinitialization"); } return instance; } try { // reset to be able to run this container then tomee one etc... if (JavaSecurityManagers.getSystemProperty(Context.URL_PKG_PREFIXES) != null) { JavaSecurityManagers.removeSystemProperty(Context.URL_PKG_PREFIXES); } final Properties properties = new Properties(); properties.putAll(map); SystemInstance.reset(); SystemInstance.init(properties); SystemInstance.get().setProperty("openejb.embedded", "true"); SystemInstance.get().setProperty(EJBContainer.class.getName(), "true"); if (SystemInstance.get().getComponent(ParentClassLoaderFinder.class) == null) { ClassLoader tccl = Thread.currentThread().getContextClassLoader(); if (tccl == null) { tccl = OpenEjbContainer.class.getClassLoader(); } SystemInstance.get().setComponent(ParentClassLoaderFinder.class, new ProvidedClassLoaderFinder(tccl)); } OptionsLog.install(); OpenEJB.init(properties); Core.warmup(); // don't do it too eagerly to avoid to not have properties DeploymentLoader.reloadAltDD(); // otherwise hard to use multiple altdd with several start/stop in the same JVM final ConfigurationFactory configurationFactory = new ConfigurationFactory(); final AppModule appModule = load(map, configurationFactory); final Set<String> callers; if (map.containsKey(OPENEJB_ADDITIONNAL_CALLERS_KEY)) { callers = new LinkedHashSet<String>(); callers.addAll(Arrays.asList(((String) map.get(OPENEJB_ADDITIONNAL_CALLERS_KEY)).split(","))); } else { callers = NewLoaderLogic.callers(); } final EjbJar ejbJar = new EjbJar(); final OpenejbJar openejbJar = new OpenejbJar(); for (final String caller : callers) { if (!isValid(caller)) { continue; } String name = caller; if (name.contains("$")) { name = caller.replace("$", "_"); } final ManagedBean bean = ejbJar.addEnterpriseBean(new ManagedBean(name, caller, true)); bean.localBean(); // set it to bean so it can get UserTransaction injection bean.setTransactionType(TransactionType.BEAN); final EjbDeployment ejbDeployment = openejbJar.addEjbDeployment(bean); // important in case any other deploment id formats are specified ejbDeployment.setDeploymentId(name); } final EjbModule ejbModule = new EjbModule(ejbJar, openejbJar); ejbModule.getProperties().setProperty("openejb.cdi.activated", "false"); // BeanManagerImpl will likely be empty ejbModule.setBeans(new Beans()); // avoid warnings but not effectvely used appModule.getEjbModules().add(ejbModule); final AppInfo appInfo; try { appInfo = configurationFactory.configureApplication(appModule); } catch (final ValidationFailedException e) { logger().warning("configureApplication.loadFailed", appModule.getModuleId(), e.getMessage()); // DO not include the stacktrace in the message throw new InvalidApplicationException(e); } catch (final OpenEJBException e) { // DO NOT REMOVE THE EXCEPTION FROM THIS LOG MESSAGE // removing this message causes NO messages to be printed when embedded logger().warning("configureApplication.loadFailed", e, appModule.getModuleId(), e.getMessage()); throw new ConfigureApplicationException(e); } final Assembler assembler = SystemInstance.get().getComponent(Assembler.class); final AppContext appContext; try { appContext = assembler.createApplication(appInfo, appModule.getClassLoader()); } catch (final ValidationException ve) { throw ve; } catch (final Exception e) { throw new AssembleApplicationException(e); } final OpenEjbContainer openEjbContainer = instance = new OpenEjbContainer(map, appContext); if (isSingleClose()) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (instance != null) { instance.doClose(); } } }); } return openEjbContainer; } catch (final OpenEJBException | MalformedURLException e) { throw new EJBException(e); } catch (final ValidationException ve) { throw ve; } catch (final Exception e) { if (e instanceof EJBException) { throw (EJBException) e; } throw new InitializationException(e); } finally { if (instance == null && OpenEJB.isInitialized()) { try { OpenEJB.destroy(); } catch (final Exception e) { // no-op } } } } private boolean isValid(final String caller) { try { final ClassLoader loader = Thread.currentThread().getContextClassLoader(); final Class<?> clazz = loader.loadClass(caller); final int modifiers = clazz.getModifiers(); return !clazz.isEnum() && !clazz.isInterface() && !Modifier.isAbstract(modifiers); } catch (final ClassNotFoundException e) { return false; } } private AppModule load(final Map<?, ?> map, final ConfigurationFactory configurationFactory) throws MalformedURLException, OpenEJBException { final List<File> moduleLocations; final String appId = (String) map.get(EJBContainer.APP_NAME); final Object modules = map.get(EJBContainer.MODULES); // will be updated if needed // ClassLoader classLoader = getClass().getClassLoader(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (modules instanceof String) { moduleLocations = configurationFactory.getModulesFromClassPath(null, classLoader); for (final Iterator<File> i = moduleLocations.iterator(); i.hasNext(); ) { final File file = i.next(); if (!match((String) modules, file)) { i.remove(); } } } else if (modules instanceof String[]) { // TODO Optimize this so we look specifically for modules by name moduleLocations = configurationFactory.getModulesFromClassPath(null, classLoader); int matched = 0; for (final Iterator<File> i = moduleLocations.iterator(); i.hasNext(); ) { final File file = i.next(); boolean remove = true; for (final String s : (String[]) modules) { if (match(s, file)) { remove = false; matched++; break; } } if (remove) { i.remove(); } } if (matched != ((String[]) modules).length) { throw specifiedModulesNotFound(); } } else if (modules instanceof File) { final URL url = ((File) modules).toURI().toURL(); classLoader = new URLClassLoader(new URL[]{url}, classLoader); moduleLocations = Collections.singletonList((File) modules); } else if (modules instanceof File[]) { final File[] files = (File[]) modules; final URL[] urls = new URL[files.length]; for (int i = 0; i < urls.length; i++) { urls[i] = files[i].toURI().toURL(); } classLoader = new URLClassLoader(urls, classLoader); moduleLocations = Arrays.asList((File[]) modules); } else if (modules == null) { moduleLocations = configurationFactory.getModulesFromClassPath(null, classLoader); } else { final AppModule appModule = load(map); if (appModule != null) { return appModule; } throw invalidModulesValue(modules); } if (moduleLocations.isEmpty()) { throw Exceptions.newNoModulesFoundException(); } // TODO With this load method we can finally do some checking on module ids to really implement EJBContainer.MODULES String/String[] return configurationFactory.loadApplication(classLoader, appId, moduleLocations); } private AppModule load(final Map<?, ?> map) { final String appId = (String) map.get(EJBContainer.APP_NAME); final Object modules = map.get(EJBContainer.MODULES); map.size(); final AppModule m; { Application application = null; AppModule appModule = new AppModule(this.getClass().getClassLoader(), appId); { if (modules instanceof EjbJar) { final EjbJar ejbJar = (EjbJar) modules; appModule.getEjbModules().add(new EjbModule(ejbJar)); } else if (modules instanceof EnterpriseBean) { final EnterpriseBean bean = (EnterpriseBean) modules; final EjbJar ejbJar = new EjbJar(); ejbJar.addEnterpriseBean(bean); appModule.getEjbModules().add(new EjbModule(ejbJar)); } else if (modules instanceof Application) { application = (Application) modules; } else if (modules instanceof Connector) { final Connector connector = (Connector) modules; appModule.getConnectorModules().add(new ConnectorModule(connector)); } else if (modules instanceof Persistence) { final Persistence persistence = (Persistence) modules; appModule.addPersistenceModule(new PersistenceModule(appModule, "", persistence)); } else if (modules instanceof PersistenceUnit) { final PersistenceUnit unit = (PersistenceUnit) modules; appModule.addPersistenceModule(new PersistenceModule(appModule, "", new Persistence(unit))); } else if (modules instanceof Beans) { final Beans beans = (Beans) modules; final EjbModule ejbModule = new EjbModule(new EjbJar()); ejbModule.setBeans(beans); appModule.getEjbModules().add(ejbModule); } } // Application is final in AppModule, which is fine, so we'll create a new one and move everything if (application != null) { final AppModule newModule = new AppModule(appModule.getClassLoader(), appModule.getModuleId(), application, false); newModule.getClientModules().addAll(appModule.getClientModules()); newModule.addPersistenceModules(appModule.getPersistenceModules()); newModule.getEjbModules().addAll(appModule.getEjbModules()); newModule.getConnectorModules().addAll(appModule.getConnectorModules()); appModule = newModule; } m = appModule; } return m; } // TODO, report some information private EJBException specifiedModulesNotFound() { return new NoSuchModuleException("some modules not matched on classpath"); } private InvalidModulesPropertyException invalidModulesValue(final Object value) { final String[] spec = {"java.lang.String", "java.lang.String[]", "java.io.File", "java.io.File[]"}; // TODO // String[] vendor = {"java.lang.Class","java.lang.Class[]", "java.net.URL", "java.io.URL[]"}; final String type = value == null ? null : value.getClass().getName(); return new InvalidModulesPropertyException(String.format("Invalid '%s' value '%s'. Valid values are: %s", EJBContainer.MODULES, type, Join.join(", ", spec))); } private static boolean isOtherProvider(final Map<?, ?> properties) { final Object provider = properties.get(EJBContainer.PROVIDER); return provider != null && !provider.equals(OpenEjbContainer.class) && !provider.equals(OpenEjbContainer.class.getName()) && !"openejb".equals(provider); } private boolean match(final String s, final File file) { final String s2 = file.getName(); final String s3 = file.getAbsolutePath(); final boolean matches; if (file.isDirectory()) { matches = s2.equals(s) || s2.equals(s + ".jar") || s3.equals(s); } else { matches = s2.equals(s + ".jar"); } // TODO if (!matches) { /* look for ejb-jar.xml with matching module name */ } return matches; } } private class GlobalContext extends ContextFlyweight { private final Context globalJndiContext; public GlobalContext(final Context globalJndiContext) { this.globalJndiContext = globalJndiContext; } @Override protected Context getContext() throws NamingException { return globalJndiContext; } @Override protected Name getName(Name name) throws NamingException { String first = name.get(0); if (!first.startsWith("java:")) { throw new NameNotFoundException("Name must be in java: namespace"); } first = first.substring("java:".length()); name = name.getSuffix(1); return name.add(0, first); } @Override protected String getName(final String name) throws NamingException { if ("inject".equals(name)) { return name; } if (!name.startsWith("java:")) { throw new NameNotFoundException("Name must be in java: namespace"); } return name.substring("java:".length()); } @Override public void bind(final Name name, final Object obj) throws NamingException { if (name.size() == 1 && "inject".equals(name.get(0))) { Injector.inject(obj); } else { super.bind(name, obj); } } @Override public void bind(final String name, final Object obj) throws NamingException { if (name != null && "inject".equals(name)) { Injector.inject(obj); } else { super.bind(name, obj); } } @Override public void unbind(final Name name) throws NamingException { if (!(name.size() == 1 && "inject".equals(name.get(0)))) { super.unbind(name); } } @Override public void unbind(final String name) throws NamingException { if (!(name != null && "inject".equals(name))) { super.unbind(name); } } } public static class InitializationException extends EJBException { public InitializationException(final String s) { super(s); } public InitializationException(final Exception cause) { super(cause); } } @SuppressWarnings("UnusedDeclaration") public static class InvalidModulesPropertyException extends InitializationException { public InvalidModulesPropertyException(final String s) { super(s); } } public static class NoSuchModuleException extends InitializationException { public NoSuchModuleException(final String s) { super(s); } } public static class NoModulesFoundException extends InitializationException { public NoModulesFoundException(final String s) { super(s); } } public static class ConfigureApplicationException extends InitializationException { public ConfigureApplicationException(final Exception cause) { super(cause); } } public static class AssembleApplicationException extends InitializationException { public AssembleApplicationException(final Exception cause) { super(cause); } } public static class InvalidApplicationException extends InitializationException { public InvalidApplicationException(final Exception cause) { super(cause); } } }