blob: bc0c6cd43b5d8bf8bd6910017f384476dcf1e85b [file] [log] [blame]
/*
* 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.tomee.catalina;
import org.apache.catalina.Cluster;
import org.apache.catalina.Container;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Loader;
import org.apache.catalina.Manager;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Realm;
import org.apache.catalina.Service;
import org.apache.catalina.UserDatabase;
import org.apache.catalina.Valve;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.WebResourceSet;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Request;
import org.apache.catalina.core.ContainerBase;
import org.apache.catalina.core.NamingContextListener;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.deploy.NamingResourcesImpl;
import org.apache.catalina.ha.CatalinaCluster;
import org.apache.catalina.ha.tcp.SimpleTcpCluster;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.Constants;
import org.apache.catalina.startup.ContextConfig;
import org.apache.catalina.startup.HostConfig;
import org.apache.catalina.startup.OpenEJBContextConfig;
import org.apache.catalina.users.MemoryUserDatabase;
import org.apache.catalina.webresources.DirResourceSet;
import org.apache.naming.ContextAccessController;
import org.apache.naming.ContextBindings;
import org.apache.naming.ResourceEnvRef;
import org.apache.naming.ResourceRef;
import org.apache.openejb.AppContext;
import org.apache.openejb.BeanContext;
import org.apache.openejb.BeanType;
import org.apache.openejb.ClassLoaderUtil;
import org.apache.openejb.Injection;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.assembler.DeployerEjb;
import org.apache.openejb.assembler.classic.AppInfo;
import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.assembler.classic.ClassListInfo;
import org.apache.openejb.assembler.classic.ConnectorInfo;
import org.apache.openejb.assembler.classic.DeploymentExceptionManager;
import org.apache.openejb.assembler.classic.EjbJarInfo;
import org.apache.openejb.assembler.classic.InjectionBuilder;
import org.apache.openejb.assembler.classic.JaccPermissionsBuilder;
import org.apache.openejb.assembler.classic.JndiEncBuilder;
import org.apache.openejb.assembler.classic.OpenEjbConfiguration;
import org.apache.openejb.assembler.classic.OpenEjbConfigurationFactory;
import org.apache.openejb.assembler.classic.PersistenceUnitInfo;
import org.apache.openejb.assembler.classic.PolicyContext;
import org.apache.openejb.assembler.classic.ReloadableEntityManagerFactory;
import org.apache.openejb.assembler.classic.ResourceInfo;
import org.apache.openejb.assembler.classic.ServletInfo;
import org.apache.openejb.assembler.classic.WebAppBuilder;
import org.apache.openejb.assembler.classic.WebAppInfo;
import org.apache.openejb.assembler.classic.event.NewEjbAvailableAfterApplicationCreated;
import org.apache.openejb.cdi.CdiAppContextsService;
import org.apache.openejb.cdi.CdiBuilder;
import org.apache.openejb.cdi.OpenEJBLifecycle;
import org.apache.openejb.cdi.Proxys;
import org.apache.openejb.config.AppModule;
import org.apache.openejb.config.ConfigurationFactory;
import org.apache.openejb.config.DeploymentLoader;
import org.apache.openejb.config.EjbModule;
import org.apache.openejb.config.TldScanner;
import org.apache.openejb.config.WebModule;
import org.apache.openejb.config.sys.Resource;
import org.apache.openejb.core.CoreContainerSystem;
import org.apache.openejb.core.ParentClassLoaderFinder;
import org.apache.openejb.core.WebContext;
import org.apache.openejb.core.ivm.IntraVmProxy;
import org.apache.openejb.core.ivm.naming.SystemComponentReference;
import org.apache.openejb.jee.EnvEntry;
import org.apache.openejb.jee.WebApp;
import org.apache.openejb.loader.Files;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.server.httpd.BeginWebBeansListener;
import org.apache.openejb.server.httpd.EndWebBeansListener;
import org.apache.openejb.server.httpd.HttpSession;
import org.apache.openejb.spi.ContainerSystem;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.URLs;
import org.apache.openejb.util.proxy.LocalBeanProxyFactory;
import org.apache.openejb.util.reflection.Reflections;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.JarScanFilter;
import org.apache.tomcat.util.descriptor.web.ApplicationParameter;
import org.apache.tomcat.util.descriptor.web.ContextEnvironment;
import org.apache.tomcat.util.descriptor.web.ContextResource;
import org.apache.tomcat.util.descriptor.web.ContextResourceLink;
import org.apache.tomcat.util.descriptor.web.ContextTransaction;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import org.apache.tomcat.util.descriptor.web.ResourceBase;
import org.apache.tomcat.util.http.CookieProcessor;
import org.apache.tomcat.util.scan.StandardJarScanFilter;
import org.apache.tomee.catalina.cdi.ServletContextHandler;
import org.apache.tomee.catalina.cdi.WebBeansThreadBindingListener;
import org.apache.tomee.catalina.cluster.ClusterObserver;
import org.apache.tomee.catalina.cluster.TomEEClusterListener;
import org.apache.tomee.catalina.environment.Hosts;
import org.apache.tomee.catalina.event.AfterApplicationCreated;
import org.apache.tomee.catalina.routing.RouterValve;
import org.apache.tomee.catalina.security.TomcatSecurityConstaintsToJaccPermissionsTransformer;
import org.apache.tomee.common.NamingUtil;
import org.apache.tomee.common.UserTransactionFactory;
import org.apache.tomee.config.TomEESystemConfig;
import org.apache.tomee.loader.TomcatHelper;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.spi.ContextsService;
import javax.ejb.spi.HandleDelegate;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.servlet.ServletContext;
import javax.servlet.SessionTrackingMode;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import static java.util.Arrays.asList;
import static org.apache.tomee.catalina.Contexts.warPath;
/**
* Web application builder.
*
* @version $Rev$ $Date$
*/
public class TomcatWebAppBuilder implements WebAppBuilder, ContextListener, ParentClassLoaderFinder {
public static final String OPENEJB_CROSSCONTEXT_PROPERTY = "openejb.crosscontext";
public static final String OPENEJB_SESSION_MANAGER_PROPERTY = "openejb.session.manager";
public static final String OPENEJB_JSESSION_ID_SUPPORT = "openejb.jsessionid-support";
public static final String OPENEJB_MYFACES_DISABLE_DEFAULT_VALUES = "openejb.myfaces.disable-default-values";
/**
* Flag for ignore context
*/
public static final String IGNORE_CONTEXT = TomcatWebAppBuilder.class.getName() + ".IGNORE";
/**
* Logger instance
*/
private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB.createChild("tomcat"), "org.apache.openejb.util.resources");
public static final String DEFAULT_J2EE_SERVER = "Apache TomEE";
public static final String OPENEJB_WEBAPP_MODULE_ID = "openejb.webapp.moduleId";
private static final boolean FORCE_RELOADABLE = SystemInstance.get().getOptions().get("tomee.force-reloadable", false);
private static final boolean SKIP_TLD = SystemInstance.get().getOptions().get("tomee.skip-tld", false);
private static final Method GET_NAMING_CONTEXT_NAME; // it just sucks but that's private
static {
try {
GET_NAMING_CONTEXT_NAME = StandardContext.class.getDeclaredMethod("getNamingContextName");
GET_NAMING_CONTEXT_NAME.setAccessible(true);
} catch (final NoSuchMethodException e) {
throw new OpenEJBRuntimeException("can't find method getNamingContextName", e);
}
}
private final Map<String, Realm> realms = new ConcurrentHashMap<>();
private final Map<ClassLoader, InstanceManager> instanceManagers = new ConcurrentHashMap<>();
/**
* Context information for web applications
*/
private final Map<String, ContextInfo> infos = new HashMap<>();
/**
* Global listener for Tomcat fired events.
*/
private final GlobalListenerSupport globalListenerSupport;
/**
* OpenEJB configuration factory instance
*/
private final ConfigurationFactory configurationFactory;
/**
* Tomcat host config elements
*/
//Key is the host name
private final Map<String, HostConfig> deployers = new TreeMap<>();
private final Hosts hosts;
/**
* Deployed web applications
*/
// todo merge this map witth the infos map above
private final Map<String, DeployedApplication> deployedApps = new TreeMap<>();
/**
* OpenEJB deployment loader instance
*/
private final DeploymentLoader deploymentLoader;
/**
* OpenEJB assembler instance
* TODO can we use the SPI interface instead?
*/
private Assembler assembler;
/**
* OpenEJB container system
* TODO can we use the SPI interface instead?
*/
private CoreContainerSystem containerSystem;
private final Map<ClassLoader, Map<String, Set<String>>> jsfClasses = new HashMap<>();
private Class<?> sessionManagerClass;
private final Set<CatalinaCluster> clusters = new HashSet<>();
private ClassLoader parentClassLoader;
private boolean initJEEInfo = true;
private final ServletContextHandler servletContextHandler;
private final boolean noHostCheck;
/**
* Creates a new web application builder
* instance.
*/
public TomcatWebAppBuilder() {
SystemInstance.get().setComponent(WebAppBuilder.class, this);
SystemInstance.get().setComponent(TomcatWebAppBuilder.class, this);
initJEEInfo = "true".equalsIgnoreCase(SystemInstance.get().getProperty(TomEESystemConfig.TOMEE_INIT_J2EE_INFO, "true"));
// TODO: re-write this bit, so this becomes part of the listener, and we register this with the mbean server.
final StandardServer standardServer = TomcatHelper.getServer();
globalListenerSupport = new GlobalListenerSupport(standardServer, this);
//Getting host config listeners
hosts = new Hosts();
SystemInstance.get().setComponent(Hosts.class, hosts);
final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
for (final Service service : standardServer.findServices()) {
if (service.getContainer() instanceof Engine) {
final Engine engine = service.getContainer();
// add the global router if relevant
final URL globalRouterConf = RouterValve.serverRouterConfigurationURL();
if (globalRouterConf != null) {
final RouterValve routerValve = new RouterValve();
routerValve.setConfigurationPath(globalRouterConf);
engine.getPipeline().addValve(routerValve);
}
parentClassLoader = engine.getParentClassLoader();
if (parentClassLoader == ClassLoader.getSystemClassLoader() && parentClassLoader != tccl) {
parentClassLoader = tccl;
engine.setParentClassLoader(tccl);
} // else assume tomcat was setup to force a classloader and then respect it
manageCluster(engine.getCluster());
hosts.setDefault(engine.getDefaultHost());
addTomEERealm(engine);
for (final Container engineChild : engine.findChildren()) {
if (engineChild instanceof StandardHost) {
final StandardHost host = (StandardHost) engineChild;
manageCluster(host.getCluster());
addTomEERealm(host);
host.getPipeline().addValve(new OpenEJBSecurityListener.RequestCapturer());
hosts.add(host);
for (final LifecycleListener listener : host.findLifecycleListeners()) {
if (listener instanceof HostConfig) {
final HostConfig hostConfig = (HostConfig) listener;
deployers.put(host.getName(), hostConfig);
}
}
}
}
}
}
SystemInstance.get().addObserver(new ClusterObserver(clusters));
final OpenEjbConfigurationFactory component = SystemInstance.get().getComponent(OpenEjbConfigurationFactory.class);
ConfigurationFactory configurationFactory = ConfigurationFactory.class.isInstance(component) ?
ConfigurationFactory.class.cast(component) : SystemInstance.get().getComponent(ConfigurationFactory.class);
if (configurationFactory == null) {
configurationFactory = new ConfigurationFactory();
}
this.configurationFactory = configurationFactory;
deploymentLoader = new DeploymentLoader();
servletContextHandler = new ServletContextHandler();
setComponentsUsedByCDI();
try { // before tomcat was using ServiceLoader or manually instantiation, now it uses SL for itself so we can be in conflict
WebSockets.setConfigurator();
} catch (final Throwable th) {
// no-op: can be another API impl, normally we are ok, this is really just a safe belt
}
noHostCheck = !Boolean.parseBoolean(SystemInstance.get().getProperty("tomee.host.check", "true"));
}
private void setComponentsUsedByCDI() {
final SystemInstance systemInstance = SystemInstance.get();
if (systemInstance.getComponent(HttpServletRequest.class) == null) {
systemInstance.setComponent(HttpServletRequest.class, Proxys.threadLocalProxy(HttpServletRequest.class, OpenEJBSecurityListener.requests, null));
}
if (systemInstance.getComponent(HttpSession.class) == null) {
systemInstance.setComponent(javax.servlet.http.HttpSession.class, Proxys.threadLocalRequestSessionProxy(OpenEJBSecurityListener.requests, null));
}
if (systemInstance.getComponent(ServletContext.class) == null) {
systemInstance.setComponent(ServletContext.class, Proxys.handlerProxy(servletContextHandler, ServletContext.class, CdiAppContextsService.FiredManually.class));
}
}
private void manageCluster(final Cluster cluster) {
if (cluster == null || cluster instanceof SimpleTomEETcpCluster) {
return;
}
Cluster current = cluster;
if (cluster instanceof SimpleTcpCluster) {
final Container container = cluster.getContainer();
current = new SimpleTomEETcpCluster((SimpleTcpCluster) cluster);
container.setCluster(current);
}
if (current instanceof CatalinaCluster) {
final CatalinaCluster haCluster = (CatalinaCluster) current;
TomEEClusterListener listener = SystemInstance.get().getComponent(TomEEClusterListener.class);
if (listener == null) {
listener = new TomEEClusterListener();
SystemInstance.get().setComponent(TomEEClusterListener.class, listener);
}
haCluster.addClusterListener(listener); // better to be a singleton
clusters.add(haCluster);
}
}
private void addTomEERealm(final Engine engine) {
final Realm realm = engine.getRealm();
if (realm != null && !(realm instanceof TomEERealm) && (engine.getParent() == null || (!realm.equals(engine.getParent().getRealm())))) {
final Realm tomeeRealm = tomeeRealm(realm);
engine.setRealm(tomeeRealm);
if (LifecycleState.STARTING_PREP.equals(engine.getState())) {
try {
Lifecycle.class.cast(tomeeRealm).start();
} catch (final LifecycleException e) {
throw new IllegalStateException(e);
}
}
}
}
private void addTomEERealm(final Host host) {
final Realm realm = host.getRealm();
if (realm != null && !(realm instanceof TomEERealm) && (host.getParent() == null || (!realm.equals(host.getParent().getRealm())))) {
host.setRealm(tomeeRealm(realm));
}
}
protected Realm tomeeRealm(final Realm realm) {
final TomEERealm trealm = new TomEERealm();
trealm.setRealmPath("/tomee");
trealm.addRealm(realm);
return trealm;
}
/**
* Start operation.
*/
public void start() {
globalListenerSupport.start();
}
/**
* Stop operation.
*/
public void stop() {
globalListenerSupport.stop();
}
@Override
public void start(final StandardServer server) {
if (SystemInstance.get().isDefaultProfile()) { // add user tomee is no user are specified
try {
final NamingResourcesImpl resources = server.getGlobalNamingResources();
final ContextResource userDataBaseResource = resources.findResource("UserDatabase");
final UserDatabase db = (UserDatabase) server.getGlobalNamingContext().lookup(userDataBaseResource.getName());
if (!db.getUsers().hasNext() && db instanceof MemoryUserDatabase) {
final MemoryUserDatabase mudb = (MemoryUserDatabase) db;
final boolean oldRo = mudb.getReadonly();
try {
((MemoryUserDatabase) db).setReadonly(false);
db.createRole("tomee-admin", "tomee admin role");
db.createUser("tomee", "tomee", "TomEE");
db.findUser("tomee").addRole(db.findRole("tomee-admin"));
} finally {
mudb.setReadonly(oldRo);
}
}
} catch (final Throwable t) {
// no-op
}
}
}
//
// OpenEJB WebAppBuilder
//
/**
* {@inheritDoc}
*/
@Override
public void deployWebApps(final AppInfo appInfo, final ClassLoader classLoader) throws Exception {
try {
for (final WebAppInfo webApp : appInfo.webApps) {
// look for context.xml
final File war = new File(webApp.path);
URL contextXmlUrl = null;
if (war.isDirectory()) {
final File cXml = new File(war, Constants.ApplicationContextXml).getAbsoluteFile();
if (cXml.exists()) {
contextXmlUrl = cXml.toURI().toURL();
LOGGER.info("using context file " + cXml.getAbsolutePath());
}
} else { // war
try (final JarFile warAsJar = new JarFile(war)) {
final JarEntry entry = warAsJar.getJarEntry(Constants.ApplicationContextXml);
if (entry != null) {
contextXmlUrl = new URL("jar:" + war.getAbsoluteFile().toURI().toURL().toExternalForm() + "!/" + Constants.ApplicationContextXml);
}
}
}
if (isAlreadyDeployed(appInfo, webApp)) {
continue;
}
StandardContext standardContext;
{
final ClassLoader containerLoader = Helper.get();
final Host host = hosts.getDefault();
if (StandardHost.class.isInstance(host) && !StandardContext.class.getName().equals(StandardHost.class.cast(host).getContextClass())) {
try {
standardContext = StandardContext.class.cast(containerLoader.loadClass(StandardHost.class.cast(host).getContextClass()).newInstance());
} catch (final Throwable th) {
LOGGER.warning("Can't use context class specified, using default StandardContext", th);
standardContext = new StandardContext();
}
} else {
standardContext = new StandardContext();
}
// should be optional but in maven parent is app loader and not maven loader which is the real parent
final ClassLoader currentParent = standardContext.getParentClassLoader();
if (currentParent == null || isParent(currentParent, containerLoader)) {
standardContext.setParentClassLoader(containerLoader);
}
}
standardContext.setUnpackWAR(!"false".equalsIgnoreCase(appInfo.properties.getProperty("tomcat.unpackWar")));
if (contextXmlUrl != null) {
standardContext.setConfigFile(contextXmlUrl);
}
// override path if needed - hack at this moment just to pass the TCK
// Tomcat should definitely implement it, but community does not seem to like the feature and therefore
// willing to implement it.
// default context path must start with / but not end with slash
try {
if (webApp.defaultContextPath != null && webApp.defaultContextPath.matches("^/\\w*[^/]$")) {
standardContext.setPath(webApp.defaultContextPath);
}
} catch (final Exception e) {
// don't fail because it's a hack, just output the exception
e.printStackTrace();
}
if (standardContext.getPath() != null) {
webApp.contextRoot = standardContext.getPath();
}
if (webApp.contextRoot.startsWith("/") || webApp.contextRoot.startsWith(File.separator)) {
webApp.contextRoot = webApp.contextRoot.substring(1);
}
if (webApp.contextRoot.startsWith(File.separator)) {
webApp.contextRoot = webApp.contextRoot.replaceFirst(File.separator, "");
}
// /!\ take care, StandardContext default host = "_" and not null or localhost
final String hostname = Contexts.getHostname(standardContext);
if (hostname != null && !"_".equals(hostname)) {
webApp.host = hostname;
}
final ApplicationParameter appParam = new ApplicationParameter();
appParam.setName(OPENEJB_WEBAPP_MODULE_ID);
appParam.setValue(webApp.moduleId);
standardContext.addApplicationParameter(appParam);
if (!isAlreadyDeployed(appInfo, webApp)) {
if (standardContext.getPath() == null) {
if (webApp.contextRoot != null && webApp.contextRoot.startsWith("/")) {
standardContext.setPath(webApp.contextRoot);
} else if (isRoot(webApp.contextRoot)) {
standardContext.setPath("");
} else {
standardContext.setPath("/" + webApp.contextRoot);
}
}
if (standardContext.getDocBase() == null) {
standardContext.setDocBase(webApp.path);
}
String docBase = standardContext.getDocBase();
File docBaseFile = new File(docBase);
if (docBase != null && docBaseFile.isFile() && docBase.endsWith(".war")) {
DeploymentLoader.unpack(docBaseFile);
if (standardContext.getPath().endsWith(".war")) {
standardContext.setPath(removeFirstSlashAndWar("/" + standardContext.getPath()));
standardContext.setName(standardContext.getPath());
webApp.contextRoot = standardContext.getPath();
}
standardContext.setDocBase(docBase.substring(0, docBase.length() - 4));
}
if (isRoot(standardContext.getName())) {
standardContext.setName("");
webApp.contextRoot = "";
}
if (isAlreadyDeployed(appInfo, webApp)) { // possible because of the previous renaming
continue;
}
// add classloader which is an URLClassLoader created by openejb
// {@see Assembler}
//
// we add it as parent classloader since we scanned classes with this classloader
// that's why we force delegate to true.
//
// However since this classloader and the webappclassloader will have a lot
// of common classes/resources we have to avoid duplicated resources
// so we contribute a custom loader.
//
// Note: the line standardContext.getLoader().setDelegate(true);
// could be hardcoded in the custom loader
// but here we have all the classloading logic
if (classLoader != null) {
standardContext.setParentClassLoader(classLoader);
standardContext.setDelegate(true);
}
String host = webApp.host;
if (host == null) {
host = hosts.getDefaultHost();
LOGGER.info("using default host: " + host);
}
if (classLoader != null) {
appInfo.autoDeploy = false;
deployWar(standardContext, host, appInfo);
} else { // force a normal deployment with lazy building of AppInfo
deployWar(standardContext, host, null);
}
// TODO should we copy the information in the appInfo using the jee object tree or add more to the info tree
// this might then move to the assembler after webapp is deployed so we can read information from info tree
// and build up all policy context from there instead of from Tomcat internal objects
final TomcatSecurityConstaintsToJaccPermissionsTransformer transformer =
new TomcatSecurityConstaintsToJaccPermissionsTransformer(standardContext);
final PolicyContext policyContext = transformer.createResourceAndDataPermissions();
final JaccPermissionsBuilder jaccPermissionsBuilder = new JaccPermissionsBuilder();
jaccPermissionsBuilder.install(policyContext);
}
}
} finally { // cleanup temp var passing
for (final WebAppInfo webApp : appInfo.webApps) {
appInfo.properties.remove(webApp);
}
}
}
private boolean isAlreadyDeployed(final AppInfo appInfo, final WebAppInfo webApp) {
final String version = appVersion(appInfo);
final ContextInfo contextInfo = getContextInfo(webApp.host, webApp.contextRoot, version);
if (contextInfo != null && contextInfo.standardContext != null && contextInfo.standardContext.getState() == LifecycleState.FAILED) {
synchronized (infos) {
infos.remove(getId(webApp.host, webApp.contextRoot, version));
}
return false;
}
return contextInfo != null;
}
private static boolean isParent(final ClassLoader parent, final ClassLoader child) {
ClassLoader current = child;
while (current != null) {
if (current == parent) {
return true;
}
current = current.getParent();
}
return child.getParent() == null && child != ClassLoader.getSystemClassLoader(); // maven ClassRealm classloader...yeah that's not awesome
}
private static boolean isRoot(final String name) {
return "/ROOT".equals(name) || "ROOT".equals(name) || name == null || name.isEmpty() || "ROOT.war".equals(name);
}
public void deployWar(final StandardContext standardContext, final String host, final AppInfo info) {
// TODO: instead of storing deployers, we could just lookup the right hostconfig for the server.
final HostConfig deployer = deployers.get(host);
if (isReady(deployer)) { // if not ready using directly host to avoid a NPE
if (info != null) {
final ContextInfo contextInfo = addContextInfo(host, standardContext);
contextInfo.appInfo = info;
contextInfo.deployer = deployer;
contextInfo.module = extractModule(standardContext, info);
}
deployer.manageApp(standardContext);
} else {
final Host theHost = hosts.get(host);
if (theHost != null) {
if (info != null) {
final ContextInfo contextInfo = addContextInfo(host, standardContext);
contextInfo.appInfo = info;
contextInfo.host = theHost;
contextInfo.module = extractModule(standardContext, info);
}
theHost.addChild(standardContext);
}
}
}
private EjbModule extractModule(final StandardContext standardContext, final AppInfo appInfo) {
for (final WebAppInfo app : appInfo.webApps) {
if (app.path != null && warPath(standardContext).equals(rootPath(new File(app.path)))) {
// see org.apache.openejb.config.ConfigurationFactory.configureApplication(java.io.File)
return EjbModule.class.cast(appInfo.properties.remove(app));
}
}
return null;
}
public synchronized ContextInfo standaAloneWebAppInfo(final File file) {
for (final ContextInfo info : infos.values()) {
if (info.appInfo != null && info.appInfo.webAppAlone
&& ((file.equals(new File(info.appInfo.path)) || file.equals(new File(info.appInfo.path + ".war"))))) {
return info;
}
if (info.standardContext != null && (file.equals(new File(info.standardContext.getDocBase())) || file.equals(new File(info.standardContext.getDocBase() + ".war")))) {
return info;
}
}
// still not found - trying another algorithm - weird but it seems to happen
final String path = file.getAbsolutePath();
for (final ContextInfo info : infos.values()) {
if (info.appInfo != null && info.appInfo.webAppAlone
&& (info.appInfo.path.endsWith(path) || (info.appInfo.path + ".war").endsWith(path))) {
return info;
}
}
return null;
}
public synchronized Collection<String> availableApps() {
final Collection<String> apps = new ArrayList<>();
for (final ContextInfo info : infos.values()) {
if (info.appInfo != null) {
apps.add(info.appInfo.path);
} else if (info.standardContext != null) {
apps.add("[not deployed] " + info.standardContext.getName());
}
}
return apps;
}
// TODO: find something more sexy
private static final AtomicReference<Field> HOST_CONFIG_HOST = new AtomicReference<>(null);
static {
try { // do it only once
HOST_CONFIG_HOST.set(HostConfig.class.getDeclaredField("host"));
} catch (final NoSuchFieldException e) {
// no-op
}
}
private static boolean isReady(final HostConfig deployer) {
if (deployer != null && HOST_CONFIG_HOST.get() != null) {
try {
return HOST_CONFIG_HOST.get().get(deployer) != null;
} catch (final Exception e) {
// no-op
}
}
return false;
}
/**
* just to avoid a lot of log lines which are often useless.
*
* @param context the servlet context to init.
*/
private static void addMyFacesDefaultParameters(final ClassLoader classLoader, final ServletContext context) {
if (!SystemInstance.get().getOptions().get(OPENEJB_MYFACES_DISABLE_DEFAULT_VALUES, false)) {
if (classLoader != null) {
try { // if myfaces is not here we doesn't need any trick
classLoader.loadClass("org.apache.myfaces.shared.config.MyfacesConfig");
} catch (final ClassNotFoundException cnfe) {
return;
}
}
setInitParameter(context, "org.apache.myfaces.LOG_WEB_CONTEXT_PARAMS", "false");
setInitParameter(context, "org.apache.myfaces.EL_RESOLVER_COMPARATOR", "org.apache.myfaces.el.unified.OpenWebBeansELResolverComparator");
setInitParameter(context, "org.apache.myfaces.EXPRESSION_FACTORY", "org.apache.el.ExpressionFactoryImpl");
}
}
private static void setInitParameter(final ServletContext context, final String key, final String value) {
if (context.getInitParameter(key) == null) {
context.setInitParameter(key, value);
}
}
/**
* {@inheritDoc}
*/
@Override
public void undeployWebApps(final AppInfo appInfo) throws Exception {
final String version = appVersion(appInfo);
for (final WebAppInfo webApp : appInfo.webApps) {
final ContextInfo contextInfo = getContextInfo(webApp.host, webApp.contextRoot, version);
if (contextInfo != null) {
final StandardContext standardContext = contextInfo.standardContext;
if (!appInfo.webAppAlone || !appInfo.properties.containsKey("tomee.destroying")) {
undeploy(standardContext, contextInfo);
final File extracted = Contexts.warPath(standardContext);
if (isExtracted(extracted)) {
deleteDir(extracted);
}
removeContextInfo(standardContext);
}
}
}
}
@SuppressWarnings("PMD.UnusedFormalParameter")
private boolean isExtracted(final File extracted) {
// TODO: do we want to delete it?
return false;
}
/**
* Deletes given directory.
*
* @param dir directory
*/
private void deleteDir(final File dir) {
if (dir == null) {
return;
}
if (dir.isFile()) {
return;
}
final File[] files = dir.listFiles();
if (files != null) {
for (final File file : files) {
if (file.isDirectory()) {
deleteDir(file);
} else {
if (!file.delete()) {
file.deleteOnExit();
}
}
}
}
if (!dir.delete()) {
dir.deleteOnExit();
}
}
/**
* {@inheritDoc}
*/
@Override
public void init(final StandardContext standardContext) {
if (isIgnored(standardContext)) {
return;
}
// just adding a carriage return to get logs more readable
LOGGER.info("------------------------- "
+ Contexts.getHostname(standardContext).replace("_", hosts.getDefaultHost()) + " -> "
+ finalName(standardContext.getPath()));
if (FORCE_RELOADABLE) {
final ContextInfo ctxInfo = getContextInfo(standardContext);
if (ctxInfo == null || (ctxInfo.appInfo != null && ctxInfo.appInfo.webAppAlone)) { // don't do it for ears
standardContext.setReloadable(true);
}
}
if (SKIP_TLD) {
if (standardContext.getJarScanner() != null && standardContext.getJarScanner().getJarScanFilter() != null) {
final JarScanFilter jarScanFilter = standardContext.getJarScanner().getJarScanFilter();
if (StandardJarScanFilter.class.isInstance(jarScanFilter)) {
StandardJarScanFilter.class.cast(jarScanFilter).setDefaultTldScan(false);
}
}
}
final String name = standardContext.getName();
initJ2EEInfo(standardContext);
File warFile = Contexts.warPath(standardContext);
if (!warFile.exists()) {
return;
}
if (!warFile.isDirectory()) {
try {
warFile = DeploymentLoader.unpack(warFile);
} catch (final OpenEJBException e) {
LOGGER.error("can't unpack '" + warFile.getAbsolutePath() + "'");
}
}
standardContext.setCrossContext(SystemInstance.get().getOptions().get(OPENEJB_CROSSCONTEXT_PROPERTY, false));
standardContext.setNamingResources(new OpenEJBNamingResource(standardContext.getNamingResources()));
String sessionManager = SystemInstance.get().getOptions().get(OPENEJB_SESSION_MANAGER_PROPERTY + "." + name, (String) null);
if (sessionManager == null) {
sessionManager = SystemInstance.get().getOptions().get(OPENEJB_SESSION_MANAGER_PROPERTY, (String) null);
}
if (sessionManager != null) {
if (sessionManagerClass == null) {
try { // the manager should be in standardclassloader
sessionManagerClass = ParentClassLoaderFinder.Helper.get().loadClass(sessionManager);
} catch (final ClassNotFoundException e) {
LOGGER.error("can't find '" + sessionManager + "', StandardManager will be used", e);
sessionManagerClass = StandardManager.class;
}
}
try {
final Manager mgr = (Manager) sessionManagerClass.newInstance();
standardContext.setManager(mgr);
} catch (final Exception e) {
LOGGER.error("can't instantiate '" + sessionManager + "', StandardManager will be used", e);
}
}
final LifecycleListener[] listeners = standardContext.findLifecycleListeners();
for (final LifecycleListener l : listeners) {
if (l instanceof ContextConfig) {
standardContext.removeLifecycleListener(l);
}
}
standardContext.addLifecycleListener(new OpenEJBContextConfig(new StandardContextInfo(standardContext)));
// force manually the namingContextListener to merge jndi in an easier way
final NamingContextListener ncl = new NamingContextListener();
try {
ncl.setName((String) GET_NAMING_CONTEXT_NAME.invoke(standardContext));
} catch (final Exception e) {
ncl.setName(getId(standardContext));
}
ncl.setExceptionOnFailedWrite(standardContext.getJndiExceptionOnFailedWrite());
standardContext.setNamingContextListener(ncl);
standardContext.addLifecycleListener(ncl);
standardContext.addLifecycleListener(new TomcatJavaJndiBinder());
// listen some events
standardContext.addContainerListener(new TomEEContainerListener());
}
public void initJ2EEInfo(final StandardContext standardContext) {
if (initJEEInfo) {
standardContext.setJ2EEServer(DEFAULT_J2EE_SERVER);
final ContextInfo contextInfo = getContextInfo(standardContext);
if (contextInfo == null || contextInfo.appInfo == null || contextInfo.appInfo.path == null) {
standardContext.setJ2EEApplication(jmxName(standardContext.getName()));
} else {
standardContext.setJ2EEApplication(jmxName(shortName(contextInfo.appInfo.path)));
}
}
}
private String jmxName(final String name) { // see javax.management.ObjectName.construct()
return name.replace(':', '_');
}
private String shortName(final String path) {
if (path.contains("/")) {
return path.substring(path.lastIndexOf('/'), path.length());
}
return path;
}
private static String finalName(final String path) {
if (isRoot(path)) {
return "/";
}
return path;
}
public ContextInfo getContextInfo(final String appName) {
ContextInfo info = null;
for (final Map.Entry<String, ContextInfo> current : infos.entrySet()) {
final String key = current.getKey();
if (key.equals(appName)) {
info = current.getValue();
break;
}
if (key.endsWith(appName)) {
info = current.getValue();
}
}
return info;
}
public class StandardContextInfo {
private final StandardContext standardContext;
public StandardContextInfo(final StandardContext standardContext) {
this.standardContext = standardContext;
if (standardContext == null) {
final Throwable throwable = new Exception("StandardContext is null").fillInStackTrace();
LOGGER.warning("StandardContext should not be null", throwable);
}
}
public AppInfo app() {
final ContextInfo contextInfo = getContextInfo(standardContext);
if (contextInfo == null) {
LOGGER.debug("No ContextInfo for StandardContext " + standardContext.getName());
return null;
}
return contextInfo.appInfo;
}
public WebAppInfo get() {
if (standardContext == null) {
return null;
}
final ContextInfo contextInfo = getContextInfo(standardContext);
if (contextInfo == null) {
LOGGER.debug("No ContextInfo for StandardContext " + standardContext.getName());
return null;
}
LOGGER.debug("contextInfo = " + contextInfo);
LOGGER.debug("standardContext = " + standardContext);
if (contextInfo.appInfo == null) {
LOGGER.debug("ContextInfo has no AppInfo for StandardContext " + standardContext.getName());
return null;
}
final String id = getId(standardContext);
for (final WebAppInfo webApp : contextInfo.appInfo.webApps) {
if (webApp == null) {
LOGGER.debug("ContextInfo.appInfo.webApps entry is null StandardContext " + standardContext.getName());
continue;
}
final String wId = getId(webApp.host, webApp.contextRoot, contextInfo.version);
if (id.equals(wId)) {
return webApp;
}
}
return null;
}
public ClassLoader loader() {
if (standardContext != null && standardContext.getLoader() != null) {
return standardContext.getLoader().getClassLoader();
}
return null;
}
@Override
public String toString() {
if (standardContext == null) {
return super.toString();
}
return "StandardContextInfo{" +
"standardContext=" + standardContext +
'}';
}
}
/**
* {@inheritDoc}
*/
@Override
public void beforeStart(final StandardContext standardContext) {
if (standardContext.getResources() != null && LazyStopStandardRoot.class.isInstance(standardContext.getResources())) {
// reset after reload
Reflections.set(standardContext, "resources", LazyStopStandardRoot.class.cast(standardContext.getResources()).getDelegate());
}
final ServletContext sc = standardContext.getServletContext();
if (sc != null && !SystemInstance.get().getOptions().get(OPENEJB_JSESSION_ID_SUPPORT, true)) {
final Set<SessionTrackingMode> defaultTrackingModes = sc.getEffectiveSessionTrackingModes();
if (defaultTrackingModes.contains(SessionTrackingMode.URL)) {
final Set<SessionTrackingMode> newModes = new HashSet<>();
newModes.remove(SessionTrackingMode.URL);
sc.setSessionTrackingModes(newModes);
}
}
initContextLoader(standardContext);
// used to add custom filters first - our arquillian integration uses it for instance
// needs to be done now (= before start event) because of addFilterMapBefore() usage
final String filters = SystemInstance.get().getProperty("org.apache.openejb.servlet.filters");
if (filters != null) {
final String[] names = filters.split(",");
for (final String name : names) {
final String[] clazzMapping = name.split("=");
final FilterDef filterDef = new FilterDef();
filterDef.setFilterClass(clazzMapping[0]);
filterDef.setFilterName(clazzMapping[0]);
standardContext.addFilterDef(filterDef);
final FilterMap filterMap = new FilterMap();
filterMap.setFilterName(clazzMapping[0]);
filterMap.addURLPattern(clazzMapping[1]);
standardContext.addFilterMapBefore(filterMap);
}
}
// mainly to get back compatibility with tomcat <= 8.0
final String cookieProcessor = SystemInstance.get().getProperty("tomee.tomcat.cookieProcessor");
if (cookieProcessor != null) {
// not that important for now if we use the container loader, we mainly want to be able to access
// the legacy one
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
final Class<?> cookieProcessorClass = contextClassLoader.loadClass(cookieProcessor.trim());
standardContext.setCookieProcessor(CookieProcessor.class.cast(cookieProcessorClass.newInstance()));
} catch (final Exception e) {
throw new IllegalArgumentException("Cannot set CookieProcessor: " + cookieProcessor);
}
}
}
private void initContextLoader(final StandardContext standardContext) {
final Loader standardContextLoader = standardContext.getLoader();
if (standardContextLoader != null
&& (
(!TomEEWebappLoader.class.equals(standardContextLoader.getClass())
&& !WebappLoader.class.equals(standardContextLoader.getClass()))
|| (WebappLoader.class.equals(standardContextLoader.getClass())
&& !WebappLoader.class.cast(standardContextLoader).getLoaderClass().startsWith("org.apache.tom")))
) {
// custom loader, we don't know it
// and since we don't have a full delegate pattern for our lazy stop loader
// simply skip lazy stop loader - normally sides effect will be an early shutdown for ears and some particular features
// only affecting the app if the classes were not laoded at all
return;
}
if (standardContextLoader != null && TomEEWebappLoader.class.isInstance(standardContextLoader)) {
standardContextLoader.setContext(standardContext);
return; // no need to replace the loader
}
// we just want to wrap it to lazy stop it (afterstop)
// to avoid classnotfound in @PreDestoy or destroyApplication()
final TomEEWebappLoader loader = new TomEEWebappLoader();
loader.setDelegate(standardContext.getDelegate());
loader.setLoaderClass(TomEEWebappClassLoader.class.getName());
final Loader lazyStopLoader = new LazyStopLoader(loader);
standardContext.setLoader(lazyStopLoader);
}
@Override
public void configureStart(final LifecycleEvent event, final StandardContext standardContext) {
final ContextTransaction contextTransaction = new ContextTransaction();
contextTransaction.setProperty(org.apache.naming.factory.Constants.FACTORY, UserTransactionFactory.class.getName());
standardContext.getNamingResources().setTransaction(contextTransaction);
if (event != null) {
// ensure NamingContext is available for eager usage (@Observes @Initialized(ApplicationScoped) for instance)
standardContext.getNamingContextListener().lifecycleEvent(event);
}
TomcatHelper.configureJarScanner(standardContext);
startInternal(standardContext);
// clear a bit log for default case
addMyFacesDefaultParameters(standardContext.getLoader().getClassLoader(), standardContext.getServletContext());
// breaks cdi
standardContext.setTldValidation(Boolean.parseBoolean(SystemInstance.get().getProperty("tomee.tld.validation", "false")));
// breaks jstl
standardContext.setXmlValidation(Boolean.parseBoolean(SystemInstance.get().getProperty("tomee.xml.validation", "false")));
}
@Override
public void start(final StandardContext standardContext) {
// nothing to do for now
}
/**
* {@inheritDoc}
*/
// @Override
private void startInternal(final StandardContext standardContext) {
if (isIgnored(standardContext)) {
return;
}
if (shouldNotDeploy(standardContext)) {
return;
}
final CoreContainerSystem cs = getContainerSystem();
final Assembler a = getAssembler();
if (a == null) {
LOGGER.warning("OpenEJB has not been initialized so war will not be scanned for nested modules " + standardContext.getPath());
return;
}
AppContext appContext = null;
//Look for context info, maybe context is already scanned
ContextInfo contextInfo = getContextInfo(standardContext);
ClassLoader classLoader = standardContext.getLoader().getClassLoader();
// bind jta before the app starts to ensure we have it in CDI
final Thread thread = Thread.currentThread();
final ClassLoader originalLoader = thread.getContextClassLoader();
thread.setContextClassLoader(classLoader);
final String listenerName = standardContext.getNamingContextListener().getName();
ContextAccessController.setWritable(listenerName, standardContext.getNamingToken());
try {
final Context comp = Context.class.cast(ContextBindings.getClassLoader().lookup("comp"));
// bind TransactionManager
final TransactionManager transactionManager = SystemInstance.get().getComponent(TransactionManager.class);
safeBind(comp, "TransactionManager", transactionManager);
// bind TransactionSynchronizationRegistry
final TransactionSynchronizationRegistry synchronizationRegistry = SystemInstance.get().getComponent(TransactionSynchronizationRegistry.class);
safeBind(comp, "TransactionSynchronizationRegistry", synchronizationRegistry);
} catch (final NamingException e) {
// no-op
} finally {
thread.setContextClassLoader(originalLoader);
ContextAccessController.setReadOnly(listenerName);
}
if (contextInfo == null) {
final AppModule appModule = loadApplication(standardContext);
appModule.getProperties().put("loader.from", "tomcat");
final boolean skipTomeeResourceWrapping = !"true".equalsIgnoreCase(SystemInstance.get().getProperty("tomee.tomcat.resource.wrap", "true"));
if (!skipTomeeResourceWrapping && OpenEJBNamingResource.class.isInstance(standardContext.getNamingResources())) {
final Collection<String> importedNames = new ArrayList<>(); // we can get the same resource twice as in tomcat
// add them to the app as resource
final boolean forceDataSourceWrapping = "true".equalsIgnoreCase(SystemInstance.get().getProperty("tomee.tomcat.datasource.wrap", "false"));
final OpenEJBNamingResource nr = (OpenEJBNamingResource) standardContext.getNamingResources();
for (final ResourceBase resource : nr.getTomcatResources()) {
final String name = resource.getName();
// already init (org.apache.catalina.core.NamingContextListener.addResource())
// skip wrapping to ensure resource consistency
final boolean isDataSource = DataSource.class.getName().equals(resource.getType());
final boolean isAlreadyCreated = ContextResource.class.isInstance(resource) && ContextResource.class.cast(resource).getSingleton() && isDataSource;
if (!importedNames.contains(name)) {
importedNames.add(name);
} else {
continue;
}
boolean found = false;
for (final ResourceInfo r : SystemInstance.get().getComponent(OpenEjbConfiguration.class).facilities.resources) {
if (r.id.equals(name)) {
nr.removeResource(name);
found = true;
LOGGER.warning(name + " resource was defined in both tomcat and tomee so removing tomcat one");
break;
}
}
if (!found) {
final Resource newResource;
if (forceDataSourceWrapping || (!isAlreadyCreated && isDataSource)) { // we forward it to TomEE datasources
newResource = new Resource(name, resource.getType());
boolean jta = false;
final Properties properties = newResource.getProperties();
final Iterator<String> params = resource.listProperties();
while (params.hasNext()) {
final String paramName = params.next();
final String paramValue = (String) resource.getProperty(paramName);
// handling some param name conversion to OpenEJB style
if ("driverClassName".equals(paramName)) {
properties.setProperty("JdbcDriver", paramValue);
} else if ("url".equals(paramName)) {
properties.setProperty("JdbcUrl", paramValue);
} else {
properties.setProperty(paramName, paramValue);
}
if ("JtaManaged".equalsIgnoreCase(paramName)) {
jta = Boolean.parseBoolean(paramValue);
}
}
if (!jta) {
properties.setProperty("JtaManaged", "false");
}
} else { // custom type, let it be created
newResource = new Resource(name, resource.getType(), "org.apache.tomee:ProvidedByTomcat");
final Properties properties = newResource.getProperties();
properties.setProperty("jndiName", newResource.getId());
properties.setProperty("appName", getId(standardContext));
properties.setProperty("factory", (String) resource.getProperty("factory"));
final Reference reference = createReference(resource);
if (reference != null) {
properties.put("reference", reference);
}
}
appModule.getResources().add(newResource);
}
}
}
if (appModule != null) {
try {
contextInfo = addContextInfo(Contexts.getHostname(standardContext), standardContext);
contextInfo.standardContext = standardContext; // ensure to do it before an exception can be thrown
contextInfo.appInfo = configurationFactory.configureApplication(appModule);
final Boolean autoDeploy = DeployerEjb.AUTO_DEPLOY.get();
contextInfo.appInfo.autoDeploy = autoDeploy == null || autoDeploy;
DeployerEjb.AUTO_DEPLOY.remove();
if (!appModule.isWebapp()) {
classLoader = appModule.getClassLoader();
} else {
final ClassLoader loader = standardContext.getLoader().getClassLoader();
if (loader instanceof TomEEWebappClassLoader) {
final TomEEWebappClassLoader tomEEWebappClassLoader = (TomEEWebappClassLoader) loader;
for (final URL url : appModule.getWebModules().iterator().next().getAddedUrls()) {
tomEEWebappClassLoader.addURL(url);
}
}
}
setFinderOnContextConfig(standardContext, appModule);
servletContextHandler.getContexts().put(classLoader, standardContext.getServletContext());
try {
appContext = a.createApplication(contextInfo.appInfo, classLoader);
} finally {
servletContextHandler.getContexts().remove(classLoader);
}
// todo add watched resources to context
eagerInitOfLocalBeanProxies(appContext.getBeanContexts(), classLoader);
} catch (final Exception e) {
LOGGER.error("Unable to deploy collapsed ear in war " + standardContext, e);
undeploy(standardContext, contextInfo);
// just to force tomee to start without EE part
if (System.getProperty(TomEESystemConfig.TOMEE_EAT_EXCEPTION_PROP) == null) {
final TomEERuntimeException tre = new TomEERuntimeException(e);
final DeploymentExceptionManager dem = SystemInstance.get().getComponent(DeploymentExceptionManager.class);
dem.saveDeploymentException(contextInfo.appInfo, tre);
throw tre;
}
return;
}
}
} else {
contextInfo.standardContext = standardContext;
if (contextInfo.module != null && contextInfo.module.getFinder() != null) { // TODO: make it more explicit or less hacky not using properties
final OpenEJBContextConfig openEJBContextConfig = findOpenEJBContextConfig(standardContext);
if (openEJBContextConfig != null) {
openEJBContextConfig.finder(contextInfo.module.getFinder(), contextInfo.module.getClassLoader());
}
}
}
final String id = getId(standardContext);
WebAppInfo webAppInfo = null;
// appInfo is null when deployment fails
if (contextInfo.appInfo != null) {
for (final WebAppInfo w : contextInfo.appInfo.webApps) {
if (id.equals(getId(w.host, w.contextRoot, contextInfo.version)) || id.equals(getId(w.host, w.moduleId, contextInfo.version))) {
if (webAppInfo == null) {
webAppInfo = w;
} else if (w.host != null && w.host.equals(Contexts.getHostname(standardContext))) {
webAppInfo = w;
}
break;
}
}
if (appContext == null) {
appContext = cs.getAppContext(contextInfo.appInfo.appId);
}
}
if (webAppInfo != null) {
if (appContext == null) {
appContext = getContainerSystem().getAppContext(contextInfo.appInfo.appId);
}
// ensure matching (see getId() usage)
webAppInfo.host = Contexts.getHostname(standardContext);
webAppInfo.contextRoot = standardContext.getPath();
// save jsf stuff
final Map<String, Set<String>> scannedJsfClasses = new HashMap<>();
for (final ClassListInfo info : webAppInfo.jsfAnnotatedClasses) {
scannedJsfClasses.put(info.name, info.list);
}
jsfClasses.put(classLoader, scannedJsfClasses);
try {
// determine the injections
final Set<Injection> injections = new HashSet<>();
injections.addAll(appContext.getInjections());
if (!contextInfo.appInfo.webAppAlone) {
updateInjections(injections, classLoader, false);
for (final BeanContext bean : appContext.getBeanContexts()) { // TODO: how if the same class in multiple webapps?
updateInjections(bean.getInjections(), classLoader, true);
}
}
injections.addAll(new InjectionBuilder(classLoader).buildInjections(webAppInfo.jndiEnc));
// merge OpenEJB jndi into Tomcat jndi
final TomcatJndiBuilder jndiBuilder = new TomcatJndiBuilder(standardContext, webAppInfo, injections);
NamingUtil.setCurrentContext(standardContext);
try {
jndiBuilder.mergeJndi();
} finally {
NamingUtil.setCurrentContext(null);
}
// create EMF included in this webapp when nested in an ear
for (final PersistenceUnitInfo unitInfo : contextInfo.appInfo.persistenceUnits) {
if (unitInfo.webappName != null && unitInfo.webappName.equals(webAppInfo.moduleId)) {
try {
final ReloadableEntityManagerFactory remf =
(ReloadableEntityManagerFactory) SystemInstance.get().getComponent(ContainerSystem.class)
.getJNDIContext().lookup(Assembler.PERSISTENCE_UNIT_NAMING_CONTEXT + unitInfo.id);
remf.overrideClassLoader(classLoader);
remf.createDelegate();
} catch (final NameNotFoundException nnfe) {
LOGGER.warning("Can't find " + unitInfo.id + " persistence unit");
}
}
}
// add WebDeploymentInfo to ContainerSystem
final WebContext webContext = new WebContext(appContext);
webContext.setServletContext(standardContext.getServletContext());
webContext.setJndiEnc(new InitialContext());
webContext.setClassLoader(classLoader);
webContext.setId(webAppInfo.moduleId);
webContext.setContextRoot(webAppInfo.contextRoot);
webContext.setHost(webAppInfo.host);
webContext.setBindings(new HashMap<String, Object>());
webContext.getInjections().addAll(injections);
appContext.getWebContexts().add(webContext);
cs.addWebContext(webContext);
standardContext.getServletContext().setAttribute("openejb.web.context", webContext);
if (!contextInfo.appInfo.webAppAlone) {
final List<BeanContext> beanContexts = assembler.initEjbs(classLoader, contextInfo.appInfo, appContext, injections, new ArrayList<BeanContext>(), webAppInfo.moduleId);
OpenEJBLifecycle.CURRENT_APP_INFO.set(contextInfo.appInfo);
servletContextHandler.getContexts().put(classLoader, standardContext.getServletContext());
try {
new CdiBuilder().build(contextInfo.appInfo, appContext, beanContexts, webContext);
} catch (final Exception e) {
final DeploymentExceptionManager dem = SystemInstance.get().getComponent(DeploymentExceptionManager.class);
if (dem != null) {
dem.saveDeploymentException(contextInfo.appInfo, e);
}
throw e;
} finally {
servletContextHandler.getContexts().remove(classLoader);
OpenEJBLifecycle.CURRENT_APP_INFO.remove();
}
assembler.startEjbs(true, beanContexts);
assembler.bindGlobals(appContext.getBindings());
eagerInitOfLocalBeanProxies(beanContexts, standardContext.getLoader().getClassLoader());
deployWebServicesIfEjbCreatedHere(contextInfo.appInfo, beanContexts);
}
// jndi bindings
webContext.getBindings().putAll(appContext.getBindings());
webContext.getBindings().putAll(getJndiBuilder(classLoader, webAppInfo, injections, appContext.getProperties()).buildBindings(JndiEncBuilder.JndiScope.comp));
final JavaeeInstanceManager instanceManager = new JavaeeInstanceManager(standardContext, webContext);
standardContext.setInstanceManager(instanceManager);
instanceManagers.put(classLoader, instanceManager);
standardContext.getServletContext().setAttribute(InstanceManager.class.getName(), standardContext.getInstanceManager());
} catch (final Exception e) {
LOGGER.error("Error merging Java EE JNDI entries in to war " + standardContext.getPath() + ": Exception: " + e.getMessage(), e);
if (System.getProperty(TomEESystemConfig.TOMEE_EAT_EXCEPTION_PROP) == null) {
final DeploymentExceptionManager dem = SystemInstance.get().getComponent(DeploymentExceptionManager.class);
if (dem != null && dem.getDeploymentException(contextInfo.appInfo) != null) {
if (RuntimeException.class.isInstance(e)) {
throw RuntimeException.class.cast(e);
}
throw new TomEERuntimeException(e);
}
}
}
final WebBeansContext webBeansContext = appContext.getWebBeansContext();
if (webBeansContext != null && webBeansContext.getBeanManagerImpl().isInUse()) {
OpenEJBLifecycle.initializeServletContext(standardContext.getServletContext(), webBeansContext);
}
}
// router
final URL routerConfig = RouterValve.configurationURL(standardContext.getServletContext());
if (routerConfig != null) {
final RouterValve filter = new RouterValve();
filter.setPrefix(standardContext.getName());
filter.setConfigurationPath(routerConfig);
standardContext.getPipeline().addValve(filter);
}
// register realm to have it in TomcatSecurityService
final Realm realm = standardContext.getRealm();
realms.put(standardContext.getName(), realm);
}
private static boolean shouldNotDeploy(final StandardContext standardContext) {
if (StandardHost.class.isInstance(standardContext.getParent())) {
final StandardHost host = StandardHost.class.cast(standardContext.getParent());
if (host.getAutoDeploy() && standardContext.getDocBase() != null &&
standardContext.getDocBase() != null &&
new File(host.getAppBaseFile(), standardContext.getDocBase()).isDirectory() && (
new File(host.getAppBaseFile(), standardContext.getDocBase() + ".ear").exists() ||
new File(host.getAppBaseFile(), standardContext.getDocBase() + ".rar").exists())
) {
LOGGER.info(String.format("Not deploying exploded directory %s as Java EE artifact exists which will be deployed.",
new File(host.getAppBaseFile(), standardContext.getPath()).getAbsolutePath()));
return true;
}
}
return false;
}
public void setFinderOnContextConfig(final StandardContext standardContext, final AppModule appModule) {
final OpenEJBContextConfig openEJBContextConfig = findOpenEJBContextConfig(standardContext);
if (openEJBContextConfig != null) {
for (final EjbModule ejbModule : appModule.getEjbModules()) {
if (ejbModule.getFile() != null && warPath(standardContext).equals(rootPath(ejbModule.getFile()))) {
openEJBContextConfig.finder(ejbModule.getFinder(), ejbModule.getClassLoader());
break;
}
}
}
}
private OpenEJBContextConfig findOpenEJBContextConfig(StandardContext standardContext) {
OpenEJBContextConfig openEJBContextConfig = null;
for (final LifecycleListener listener : standardContext.findLifecycleListeners()) {
if (OpenEJBContextConfig.class.isInstance(listener)) {
openEJBContextConfig = OpenEJBContextConfig.class.cast(listener);
break;
}
}
return openEJBContextConfig;
}
private static File rootPath(final File file) {
if (file.isDirectory() && file.getName().equals("classes") && file.getParentFile() != null && file.getParentFile().getName().equals("WEB-INF")) {
final File parentFile = file.getParentFile().getParentFile();
try {
return parentFile.getCanonicalFile();
} catch (final IOException e) {
return parentFile;
}
}
try {
return file.getCanonicalFile();
} catch (final IOException e) {
return file;
}
}
private static void deployWebServicesIfEjbCreatedHere(final AppInfo info, final Collection<BeanContext> beanContexts) {
if (beanContexts == null || beanContexts.isEmpty()) {
return;
}
SystemInstance.get().fireEvent(new NewEjbAvailableAfterApplicationCreated(info, beanContexts));
}
private static void eagerInitOfLocalBeanProxies(final Collection<BeanContext> beans, final ClassLoader classLoader) {
for (final BeanContext deployment : beans) {
if (deployment.isLocalbean() && !deployment.isDynamicallyImplemented()) { // init proxy eagerly otherwise deserialization of serialized object can't work
final List<Class> interfaces = new ArrayList<>(2);
interfaces.add(Serializable.class);
interfaces.add(IntraVmProxy.class);
final BeanType type = deployment.getComponentType();
if (BeanType.STATEFUL.equals(type) || BeanType.MANAGED.equals(type)) {
interfaces.add(BeanContext.Removable.class);
}
try {
LocalBeanProxyFactory.createProxy(deployment.getBeanClass(), classLoader, interfaces.toArray(new Class<?>[interfaces.size()]));
} catch (final Exception e) {
// no-op: as before
}
}
}
}
private static Reference createReference(final ResourceBase resource) {
final Reference ref;
if (resource instanceof ContextResource) {
final ContextResource cr = (ContextResource) resource;
ref = new ResourceRef(resource.getType(), resource.getDescription(), cr.getScope(), cr.getAuth(), cr.getSingleton());
} else {
ref = new ResourceEnvRef(resource.getType());
}
final Iterator<String> params = resource.listProperties();
while (params.hasNext()) {
final String paramName = params.next();
final String paramValue = (String) resource.getProperty(paramName);
final StringRefAddr refAddr = new StringRefAddr(paramName, paramValue);
ref.add(refAddr);
}
return ref;
}
private static void updateInjections(final Collection<Injection> injections, final ClassLoader classLoader, final boolean keepInjection) {
final Iterator<Injection> it = injections.iterator();
final List<Injection> newOnes = new ArrayList<>();
while (it.hasNext()) {
final Injection injection = it.next();
if (injection.getTarget() == null) {
try {
final Class<?> target = classLoader.loadClass(injection.getClassname());
if (keepInjection) {
final Injection added = new Injection(injection.getJndiName(), injection.getName(), target);
newOnes.add(added);
} else {
injection.setTarget(target);
}
} catch (final ClassNotFoundException cnfe) {
// ignored
}
}
}
if (!newOnes.isEmpty()) {
injections.addAll(newOnes);
}
}
// return true if the dir can be deleted. TODO: revisit this heuristic
private static boolean undeploy(final StandardContext standardContext, final ContextInfo contextInfo) {
if (isReady(contextInfo.deployer)) {
contextInfo.deployer.unmanageApp(standardContext.getName());
return true;
} else if (contextInfo.host != null) {
return undeploy(standardContext, contextInfo.host);
} else {
Container container = contextInfo.standardContext;
while (container != null) {
if (container instanceof Host) {
break;
}
container = container.getParent();
}
return container != null && undeploy(standardContext, container);
}
}
private static boolean undeploy(final StandardContext standardContext, final Container host) {
final Container child = host.findChild(standardContext.getName());
// skip undeployment if redeploying (StandardContext.redeploy())
if (child instanceof org.apache.catalina.Context && org.apache.catalina.Context.class.cast(child).getPaused()) {
return true;
}
// skip undeployment if restarting
final TomEEWebappClassLoader tomEEWebappClassLoader = lazyClassLoader(
org.apache.catalina.Context.class.isInstance(child) ? org.apache.catalina.Context.class.cast(child) : null);
if (tomEEWebappClassLoader != null && tomEEWebappClassLoader.isRestarting()) {
return true;
}
if (child != null) {
host.removeChild(standardContext);
return true;
}
return false;
}
private static TomEEWebappClassLoader lazyClassLoader(final org.apache.catalina.Context child) {
if (child == null) {
return null;
}
final Loader loader = child.getLoader();
if (loader == null || !(loader instanceof LazyStopLoader)) {
return null;
}
final ClassLoader old = ((LazyStopLoader) loader).getStopClassLoader();
if (old == null || !(old instanceof TomEEWebappClassLoader)) {
return null;
}
return (TomEEWebappClassLoader) old;
}
private JndiEncBuilder getJndiBuilder(final ClassLoader classLoader, final WebAppInfo webAppInfo, final Set<Injection> injections, final Properties props) throws OpenEJBException {
return new JndiEncBuilder(webAppInfo.jndiEnc, injections, webAppInfo.moduleId, "Bean", null, webAppInfo.uniqueId, classLoader, props);
}
/**
* {@inheritDoc}
*/
@Override
public void afterStart(final StandardContext standardContext) {
if (isIgnored(standardContext)) {
return;
}
if (shouldNotDeploy(standardContext)) {
return;
}
final Realm realm = standardContext.getRealm();
final ClassLoader classLoader = standardContext.getLoader().getClassLoader();
final Thread thread = Thread.currentThread();
final ClassLoader originalLoader = thread.getContextClassLoader();
if (realm != null && !(realm instanceof TomEERealm) && (standardContext.getParent() == null || (!realm.equals(standardContext.getParent().getRealm())))) {
thread.setContextClassLoader(classLoader);
try {
standardContext.setRealm(tomeeRealm(realm));
} finally {
thread.setContextClassLoader(originalLoader);
}
}
// if appInfo is null this is a failed deployment... just ignore
final ContextInfo contextInfo = getContextInfo(standardContext);
contextInfo.module = null; // shouldnt be there after startup (actually we shouldnt need it from info tree but our scanning does)
if (contextInfo != null && contextInfo.appInfo == null) {
return;
} else if (contextInfo == null) { // openejb webapp loaded from the LoaderServlet
return;
}
final String id = getId(standardContext);
WebAppInfo currentWebAppInfo = null;
for (final WebAppInfo webAppInfo : contextInfo.appInfo.webApps) {
final String wId = getId(webAppInfo.host, webAppInfo.contextRoot, contextInfo.version);
if (id.equals(wId)) {
currentWebAppInfo = webAppInfo;
break;
}
}
// bind extra stuff at the java:comp level which can only be
// bound after the context is created
thread.setContextClassLoader(classLoader);
final NamingContextListener ncl = standardContext.getNamingContextListener();
final String listenerName = ncl.getName();
ContextAccessController.setWritable(listenerName, standardContext.getNamingToken());
try {
final Context openejbContext = (Context) getContainerSystem().getJNDIContext().lookup("openejb");
final Context root = (Context) ContextBindings.getClassLoader().lookup("");
final Context comp = (Context) ContextBindings.getClassLoader().lookup("comp"); // usually fails
// Context root = ncl.getNamingContext();
// Context comp = (Context) root.lookup("comp");
safeBind(root, "openejb", openejbContext);
// add context to WebDeploymentInfo
if (currentWebAppInfo != null) {
final WebContext webContext = getContainerSystem().getWebContext(currentWebAppInfo.moduleId);
if (webContext != null) {
webContext.setJndiEnc(root);
}
try {
// Bean Validation
standardContext.getServletContext().setAttribute("javax.faces.validator.beanValidator.ValidatorFactory", openejbContext.lookup(Assembler.VALIDATOR_FACTORY_NAMING_CONTEXT.replaceFirst("openejb", "") + currentWebAppInfo.uniqueId));
} catch (final NamingException ne) {
LOGGER.warning("no validator factory found for webapp " + currentWebAppInfo.moduleId);
}
}
try {
final Class<?> orb = TomcatWebAppBuilder.class.getClassLoader().loadClass("org.omg.CORBA.ORB");
if (SystemInstance.get().getComponent(orb) != null) {
safeBind(comp, "ORB", new SystemComponentReference(orb));
}
} catch (final NoClassDefFoundError | ClassNotFoundException cnfe) {
// no-op
}
if (SystemInstance.get().getComponent(HandleDelegate.class) != null) {
safeBind(comp, "HandleDelegate", new SystemComponentReference(HandleDelegate.class));
}
} catch (final NamingException e) {
// no-op
} finally {
// required for Pojo Web Services because when Assembler creates the application
// the CoreContainerSystem does not contain the WebContext
// see also the start method getContainerSystem().addWebDeployment(webContext);
try {
servletContextHandler.getContexts().put(classLoader, standardContext.getServletContext());
for (final WebAppInfo webAppInfo : contextInfo.appInfo.webApps) {
final String wId = getId(webAppInfo.host, webAppInfo.contextRoot, contextInfo.version);
if (id.equals(wId)) {
// Allow any post-deployment to happen without the RequestContext of a call to /tomee/ejb
final Request request = OpenEJBSecurityListener.requests.get();
OpenEJBSecurityListener.requests.remove();
SystemInstance.get().fireEvent(
new AfterApplicationCreated(contextInfo.appInfo,
webAppInfo,
standardContext.getServletContext()));
if (request != null) {
OpenEJBSecurityListener.requests.set(request);
}
break;
}
}
} finally {
servletContextHandler.getContexts().remove(classLoader);
}
thread.setContextClassLoader(originalLoader);
ContextAccessController.setReadOnly(listenerName);
}
thread.setContextClassLoader(classLoader);
try {
// owb integration filters
final WebBeansContext webBeansContext = getWebBeansContext(contextInfo);
if (webBeansContext != null) {
// it is important to have a begin and a end listener
// to be sure to create contexts before other listeners
// and destroy contexts after other listeners
final BeginWebBeansListener beginWebBeansListener = new BeginWebBeansListener(webBeansContext);
final EndWebBeansListener endWebBeansListener = new EndWebBeansListener(webBeansContext);
{
final Object[] appEventListeners = standardContext.getApplicationEventListeners();
final Object[] newEventListeners = new Object[appEventListeners.length + 2];
newEventListeners[0] = beginWebBeansListener;
System.arraycopy(appEventListeners, 0, newEventListeners, 1, appEventListeners.length);
newEventListeners[newEventListeners.length - 1] = endWebBeansListener;
standardContext.setApplicationEventListeners(newEventListeners);
}
{
final Object[] lifecycleListeners = standardContext.getApplicationLifecycleListeners();
final Object[] newLifecycleListeners = new Object[lifecycleListeners.length + 2];
newLifecycleListeners[0] = beginWebBeansListener;
System.arraycopy(lifecycleListeners, 0, newLifecycleListeners, 1, lifecycleListeners.length);
newLifecycleListeners[newLifecycleListeners.length - 1] = endWebBeansListener;
standardContext.setApplicationLifecycleListeners(newLifecycleListeners);
}
// also add the ThreadBindingListener to clean up async thread executions
{
WebBeansThreadBindingListener webBeansThreadBindingListener = new WebBeansThreadBindingListener(webBeansContext, standardContext.getThreadBindingListener());
standardContext.setThreadBindingListener(webBeansThreadBindingListener);
}
final ContextsService contextsService = webBeansContext.getContextsService();
if (CdiAppContextsService.class.isInstance(contextsService)) { // here ServletContext is usable
CdiAppContextsService.class.cast(contextsService).applicationStarted(standardContext.getServletContext());
}
} else {
// just add the end listener to be able to stack tasks to execute at the request end
final EndWebBeansListener endWebBeansListener = new EndWebBeansListener(webBeansContext);
{
final Object[] appEventListeners = standardContext.getApplicationEventListeners();
final Object[] newEventListeners = new Object[appEventListeners.length + 1];
System.arraycopy(appEventListeners, 0, newEventListeners, 0, appEventListeners.length);
newEventListeners[newEventListeners.length - 1] = endWebBeansListener;
standardContext.setApplicationEventListeners(newEventListeners);
}
{
final Object[] lifecycleListeners = standardContext.getApplicationLifecycleListeners();
final Object[] newLifecycleListeners = new Object[lifecycleListeners.length + 1];
System.arraycopy(lifecycleListeners, 0, newLifecycleListeners, 0, lifecycleListeners.length);
newLifecycleListeners[newLifecycleListeners.length - 1] = endWebBeansListener;
standardContext.setApplicationLifecycleListeners(newLifecycleListeners);
}
}
} finally {
thread.setContextClassLoader(originalLoader);
}
LinkageErrorProtection.preload(standardContext);
final Pipeline pipeline = standardContext.getPipeline();
pipeline.addValve(new OpenEJBValve());
final String[] valves = SystemInstance.get().getOptions().get("tomee.valves", "").split(" *, *");
for (final String className : valves) {
if ("".equals(className)) {
continue;
}
try {
final Class<?> clazz = classLoader.loadClass(className);
if (Valve.class.isAssignableFrom(clazz)) {
final Valve valve = (Valve) clazz.newInstance();
pipeline.addValve(valve);
}
} catch (final Exception e) {
LOGGER.error("can't add the valve " + className, e);
}
}
// add servlets to webappinfo
if (currentWebAppInfo != null) {
for (final String mapping : standardContext.findServletMappings()) {
final ServletInfo info = new ServletInfo();
info.servletName = standardContext.findServletMapping(mapping);
info.mappings.add(mapping);
final Container container = standardContext.findChild(info.servletName);
if (container instanceof StandardWrapper) {
info.servletClass = ((StandardWrapper) container).getServletClass();
} else {
info.servletClass = mapping;
}
currentWebAppInfo.servlets.add(info);
}
}
addConfiguredDocBases(standardContext, contextInfo);
ensureMyFacesDontLooseFacesContext(standardContext);
}
private void ensureMyFacesDontLooseFacesContext(final StandardContext standardContext) {
for (final Container w : standardContext.findChildren()) {
if (!Wrapper.class.isInstance(w)) {
continue;
}
final Wrapper wrapper = Wrapper.class.cast(w);
if ("FacesServlet".equals(wrapper.getName()) && "javax.faces.webapp.FacesServlet".equals(wrapper.getServletClass())) {
final ClassLoader loader = standardContext.getLoader().getClassLoader();
try {
if (Files.toFile(loader.getResource("javax/faces/webapp/FacesServlet.class")).getName().startsWith("myfaces")) {
loader.loadClass("org.apache.tomee.myfaces.TomEEWorkaroundFacesServlet");
wrapper.setServletClass("org.apache.tomee.myfaces.TomEEWorkaroundFacesServlet");
break;
}
} catch (final Throwable t) {
// not there, not a big deal in most of cases
}
}
}
}
private static String appVersion(final AppInfo appInfo) {
if (appInfo != null && appInfo.webAppAlone && appInfo.appId != null) {
final int versionIndex = appInfo.appId.indexOf("##");
return versionIndex >= 0 ? appInfo.appId.substring(versionIndex) : "";
}
return "";
}
private void addConfiguredDocBases(final StandardContext standardContext, final ContextInfo contextInfo) {
if (contextInfo.appInfo.path != null) { // add external web resources
final String contextPath = standardContext.getServletContext().getContextPath();
final String name = contextPath.isEmpty() ? "ROOT" : contextPath.substring(1);
final String webResources = SystemInstance.get().getProperty("tomee." + name + ".docBases", contextInfo.appInfo.properties.getProperty("docBases"));
if (webResources != null) {
for (final String alt : webResources.trim().split(",")) {
final String trim = alt.trim();
if (trim.isEmpty()) {
continue;
}
if (!new File(trim).isDirectory()) {
LOGGER.warning("Can't add docBase which are not directory: " + trim);
continue;
}
final WebResourceRoot root = standardContext.getResources();
root.addPreResources(new DirResourceSet(root, "/", trim, "/"));
}
}
}
}
private WebBeansContext getWebBeansContext(final ContextInfo contextInfo) {
final AppContext appContext = getContainerSystem().getAppContext(contextInfo.appInfo.appId);
if (appContext == null) {
return null;
}
WebBeansContext webBeansContext = appContext.getWebBeansContext();
if (webBeansContext == null) {
return null;
}
for (final WebContext web : appContext.getWebContexts()) {
final String stdName = removeFirstSlashAndWar(contextInfo.standardContext.getName());
if (stdName == null) {
continue;
}
final String name = removeFirstSlashAndWar(web.getContextRoot());
if (stdName.equals(name)) {
webBeansContext = web.getWebbeansContext();
if (Contexts.getHostname(contextInfo.standardContext).equals(web.getHost())) {
break;
} // else loop hoping to find a better matching
}
}
if (webBeansContext == null) {
webBeansContext = appContext.getWebBeansContext();
}
return webBeansContext;
}
private static String removeFirstSlashAndWar(final String name) {
if (name == null || "/".equals(name) || name.isEmpty()) {
return "";
}
String out = name;
if (out.startsWith("/")) {
out = out.substring(1);
}
if (out.endsWith(".war")) {
return out.substring(0, Math.max(out.length() - 4, 0));
}
return out;
}
private static boolean isIgnored(final StandardContext standardContext) {
// useful to disable web applications deployment
// it can be placed in the context.xml file, server.xml, ...
// see http://tomcat.apache.org/tomcat-5.5-doc/config/context.html#Context_Parameters
return standardContext.getServletContext().getAttribute(IGNORE_CONTEXT) != null
|| standardContext.getServletContext().getInitParameter(IGNORE_CONTEXT) != null
|| standardContext instanceof IgnoredStandardContext
|| isExcludedBySystemProperty(standardContext);
}
private static boolean isExcludedBySystemProperty(final StandardContext standardContext) {
String name = standardContext.getName();
if (name == null) {
name = standardContext.getPath();
if (name == null) { // possible ?
name = "";
}
}
if (name.startsWith("/")) {
name = name.substring(1);
}
final SystemInstance systemInstance = SystemInstance.get();
return "true".equalsIgnoreCase(systemInstance.getProperty(name + ".tomcat-only", systemInstance.getProperty("tomcat-only", "false")));
}
/**
* {@inheritDoc}
*/
@Override
public void beforeStop(final StandardContext standardContext) {
final ClassLoader classLoader = standardContext.getLoader().getClassLoader();
// if it is not our custom loader clean up now otherwise wait afterStop
if (!(standardContext.getLoader() instanceof LazyStopLoader)) {
jsfClasses.remove(classLoader);
}
// ensure we can stop it lazily - before all was in the classloader, now we align webresourceroot on the classloader config
// we wrap it only here to not modify at all runtime
if (!LazyStopStandardRoot.class.isInstance(standardContext.getResources())
&& TomEEWebappClassLoader.class.isInstance(classLoader) && !TomEEWebappClassLoader.class.cast(classLoader).isForceStopPhase()) {
final LazyStopStandardRoot standardRoot = new LazyStopStandardRoot(standardContext.getResources());
Reflections.set(standardContext, "resources", standardRoot);
TomEEWebappClassLoader.class.cast(classLoader).setWebResourceRoot(standardRoot); // cause Assembler relies on the classloader only
}
}
private boolean isUnDeployable(final ContextInfo contextInfo) {
return contextInfo.appInfo != null && contextInfo.deployer == null && contextInfo.appInfo.webAppAlone;
}
/**
* {@inheritDoc}
*/
@Override
public void stop(final StandardContext standardContext) {
//No operation
}
/**
* {@inheritDoc}
*/
@Override
public void afterStop(final StandardContext standardContext) {
if (isIgnored(standardContext)) {
return;
}
final TomEEWebappClassLoader old = lazyClassLoader(standardContext);
if (old != null) { // should always be the case
TldScanner.forceCompleteClean(old);
jsfClasses.remove(old);
}
final ContextInfo contextInfo = getContextInfo(standardContext);
boolean destroyFromTomcat = contextInfo != null && getAssembler().getDeployedApplications().contains(contextInfo.appInfo);
if (destroyFromTomcat && isUnDeployable(contextInfo)) {
contextInfo.appInfo.properties.setProperty("tomee.destroying", "true");
try {
getAssembler().destroyApplication(contextInfo.appInfo.path);
} catch (final Exception e) {
LOGGER.error("Unable to stop web application " + standardContext.getPath() + ": Exception: " + e.getMessage(), e);
}
} else {
destroyFromTomcat = false;
}
NamingUtil.cleanUpContextResource(standardContext);
if (old != null) {
if (destroyFromTomcat) {
try {
old.internalStop();
} catch (final LifecycleException e) {
LOGGER.error("error stopping classloader of webapp " + standardContext.getName(), e);
}
ClassLoaderUtil.cleanOpenJPACache(old);
}
instanceManagers.remove(old);
} else if (standardContext.getLoader() != null && standardContext.getLoader().getClassLoader() != null) {
final ClassLoader classLoader = standardContext.getLoader().getClassLoader();
instanceManagers.remove(classLoader);
}
realms.remove(standardContext.getName());
if (contextInfo != null && (contextInfo.appInfo == null || contextInfo.appInfo.webAppAlone)) {
removeContextInfo(standardContext);
}
}
/**
* {@inheritDoc}
*/
@Override
public void destroy(final StandardContext standardContext) {
final Loader standardContextLoader = standardContext.getLoader();
if (LazyStopLoader.class.isInstance(standardContextLoader)) {
final Loader delegate = LazyStopLoader.class.cast(standardContextLoader).getDelegateLoader();
if (TomEEWebappLoader.class.isInstance(delegate)) {
final TomEEWebappLoader webappLoader = TomEEWebappLoader.class.cast(delegate);
final ClassLoader loader = webappLoader.internalLoader();
webappLoader.clearLoader();
if (TomEEWebappClassLoader.class.isInstance(loader)) {
TomEEWebappClassLoader.class.cast(loader).internalDestroy();
}
}
}
final WebResourceRoot root = standardContext.getResources();
if (LazyStopStandardRoot.class.isInstance(root)) {
try {
LazyStopStandardRoot.class.cast(root).internalDestroy();
} catch (final LifecycleException e) {
throw new IllegalStateException(e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void afterStop(final StandardServer standardServer) {
// clean ear based webapps after shutdown
for (final ContextInfo contextInfo : infos.values()) {
if (contextInfo != null && contextInfo.deployer != null) {
final StandardContext standardContext = contextInfo.standardContext;
final HostConfig deployer = contextInfo.deployer;
deployer.unmanageApp(standardContext.getPath());
final File realPath = Contexts.warPath(standardContext);
if (realPath != null) {
deleteDir(realPath);
}
}
}
TomcatLoader.destroy();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void checkHost(final StandardHost standardHost) {
if (noHostCheck) {
return;
}
if (standardHost.getAutoDeploy()) {
// Undeploy any modified application
for (final Iterator<Map.Entry<String, DeployedApplication>> iterator = deployedApps.entrySet().iterator(); iterator.hasNext(); ) {
final Map.Entry<String, DeployedApplication> entry = iterator.next();
final DeployedApplication deployedApplication = entry.getValue();
if (deployedApplication.isModified()) { // TODO: for war use StandardContext.redeploy()
if (deployedApplication.appInfo != null) { // can happen with badly formed config
try {
getAssembler().destroyApplication(deployedApplication.appInfo.path);
} catch (final Exception e) {
LOGGER.error("Unable to application " + deployedApplication.appInfo.path, e);
}
} else {
LOGGER.error("appinfo is null for " + deployedApplication);
}
iterator.remove();
}
}
// Deploy new applications
final File appBase = appBase(standardHost);
final File[] files = appBase.listFiles();
if (null != files) {
for (File file : files) {
if (file.getName().endsWith(".tmp")) { // tomcat is uploading, see org.apache.catalina.manager.ManagerServlet.deploy(java.io.PrintWriter, org.apache.catalina.util.ContextName, java.lang.String, boolean, javax.servlet.http.HttpServletRequest, org.apache.tomcat.util.res.StringManager)
continue;
}
final String name = file.getName();
// ignore war files
if (name.toLowerCase().endsWith(".war") || isRoot(name)
|| name.equalsIgnoreCase("META-INF") || name.equalsIgnoreCase("WEB-INF")) {
continue;
}
// Simple fix for TOMEE-23
if (name.toLowerCase().equals(".ds_store")) {
continue;
}
// ignore unpacked web apps
if (file.isDirectory() && new File(file, "WEB-INF").exists()) {
continue;
}
// ignore unpacked apps where packed version is present (packed version is owner)
if (file.isDirectory() && (new File(file.getParent(), file.getName() + ".ear").exists()
|| new File(file.getParent(), file.getName() + ".war").exists()
|| new File(file.getParent(), file.getName() + ".rar").exists())) {
continue;
}
// ignore already deployed apps
if (isDeployed(file, standardHost)) {
continue;
}
final AppInfo appInfo;
try {
file = file.getCanonicalFile().getAbsoluteFile();
final AppModule appModule = deploymentLoader.load(file, null);
// Ignore any standalone web modules - this happens when the app is unpaked and doesn't have a WEB-INF dir
if (appModule.getDeploymentModule().size() == 1 && appModule.getWebModules().size() == 1) {
final WebModule webModule = appModule.getWebModules().iterator().next();
if (file.getAbsolutePath().equals(webModule.getJarLocation())) {
continue;
}
}
// tell web modules to deploy using this host
for (final WebModule webModule : appModule.getWebModules()) {
webModule.setHost(standardHost.getName());
}
appInfo = configurationFactory.configureApplication(appModule);
if (file.isFile() && file.getName().toLowerCase().endsWith(".ear")) {
// this is to prevent any WARs inside the EARs being unpacked in a directory where they'll then be deployed again
appInfo.properties.setProperty("tomcat.unpackWar", "false");
}
// if this is an unpacked dir, tomcat will pick it up as a webapp so undeploy it first
if (file.isDirectory()) {
final ContainerBase context = (ContainerBase) standardHost.findChild("/" + name);
if (context != null) {
try {
standardHost.removeChild(context);
} catch (final Throwable t) {
LOGGER.warning("Error undeploying wep application from Tomcat " + name, t);
}
try {
context.destroy();
} catch (final Throwable t) {
LOGGER.warning("Error destroying Tomcat web context " + name, t);
}
}
}
getAssembler().createApplication(appInfo);
deployedApps.put(file.getAbsolutePath(), new DeployedApplication(file, appInfo));
} catch (final Throwable e) {
LOGGER.warning("Error deploying application " + file.getAbsolutePath(), e);
}
}
}
}
}
/**
* Returns true if given application is deployed
* false otherwise.
*
* @param file web application file
* @param standardHost host
* @return true if given application is deployed
*/
private boolean isDeployed(final File file, final StandardHost standardHost) {
if (deployedApps.containsKey(file.getAbsolutePath())) {
return true;
}
// check if this is a deployed web application
String name = "/" + file.getName();
// ROOT context is a special case
if (isRoot(name)) {
name = "";
}
// can be a dir or a war so exists is fine
return file.exists() && standardHost.findChild(name) != null;
}
/**
* Returns application base of the given host.
*
* @param standardHost tomcat host
* @return application base of the given host
*/
protected File appBase(final StandardHost standardHost) {
File file = new File(standardHost.getAppBase());
if (!file.isAbsolute()) {
file = new File(System.getProperty("catalina.base"), standardHost.getAppBase());
}
try {
file = file.getCanonicalFile();
} catch (final IOException e) {
LOGGER.debug(e.getMessage(), e);
}
return file;
}
/**
* Creates an openejb {@link AppModule} instance
* from given tomcat context.
*
* @param standardContext tomcat context instance
* @return a openejb application module
*/
private AppModule loadApplication(final StandardContext standardContext) {
// don't use getId since the app id shouldnt get the host (jndi)
// final TomcatDeploymentLoader tomcatDeploymentLoader = new TomcatDeploymentLoader(standardContext, getId(standardContext));
String id = standardContext.getName();
if (id.startsWith("/")) {
id = id.substring(1);
}
final TomcatDeploymentLoader tomcatDeploymentLoader = new TomcatDeploymentLoader(standardContext, id);
final AppModule appModule;
try {
appModule = tomcatDeploymentLoader.load(Contexts.warPath(standardContext), configuredClasspath(standardContext));
} catch (final OpenEJBException e) {
throw new TomEERuntimeException(e);
}
// create the web module
loadWebModule(appModule, standardContext);
return appModule;
}
private static DeploymentLoader.ExternalConfiguration configuredClasspath(final StandardContext standardContext) {
Loader loader = standardContext.getLoader();
if (loader != null && LazyStopLoader.class.isInstance(loader)) {
loader = LazyStopLoader.class.cast(loader).getDelegateLoader();
}
if (loader != null) {
final ClassLoader cl = standardContext.getLoader().getClassLoader();
if (cl == null) {
return null;
}
final Collection<String> cp = new LinkedList<>();
final WebResourceRoot webResources = standardContext.getResources();
if (webResources != null) { // to enhance
for (final WebResourceSet[] sets : asList(webResources.getPreResources(), webResources.getPostResources(), webResources.getJarResources())) {
for (final WebResourceSet wr : sets) {
final URL base = wr.getBaseUrl();
if (base != null) {
final File baseFile = URLs.toFile(base);
if (baseFile.isDirectory()) {
final String[] libs = wr.list("/WEB-INF/lib/");
if (libs != null) {
for (final String resource : libs) {
cp.add(new File(baseFile, resource).getAbsolutePath());
}
}
final WebResource classes = wr.getResource("/WEB-INF/classes/");
if (classes != null) {
final String path = classes.getCanonicalPath();
if (path != null) {
cp.add(path);
}
}
} else if (baseFile.exists() && baseFile.getName().endsWith(".jar") && wr.getResource("/WEB-INF/classes/").exists()) {
try {
cp.add(baseFile.getCanonicalPath());
} catch (final IOException e) {
throw new IllegalStateException(e);
}
}
}
}
}
}
if (!cp.isEmpty()) {
return new DeploymentLoader.ExternalConfiguration(cp.toArray(new String[cp.size()]), null /*for now doesnt make sense, todo: configure*/);
}
}
return null;
}
/**
* Creates a new {@link WebModule} instance from given
* tomcat context instance.
*
* @param standardContext tomcat context instance
*/
private void loadWebModule(final AppModule appModule, final StandardContext standardContext) {
final List<WebModule> webModules = appModule.getWebModules();
if (webModules.isEmpty()) {
final File file = appModule.getFile();
LOGGER.error("Failed to find a single module in: " + file);
return;
}
final WebModule webModule = webModules.get(0);
final WebApp webApp = webModule.getWebApp();
// create the web module
final String path = standardContext.getPath();
LOGGER.debug("context path = " + path);
webModule.setHost(Contexts.getHostname(standardContext));
// Add all Tomcat env entries to context so they can be overriden by the env.properties file
final NamingResourcesImpl naming = standardContext.getNamingResources();
for (final ContextEnvironment environment : naming.findEnvironments()) {
EnvEntry envEntry = webApp.getEnvEntryMap().get(environment.getName());
if (envEntry == null) {
envEntry = new EnvEntry();
envEntry.setName(environment.getName());
webApp.getEnvEntry().add(envEntry);
}
envEntry.setEnvEntryValue(environment.getValue());
envEntry.setEnvEntryType(environment.getType());
}
// remove all jndi entries where there is a configured Tomcat resource or resource-link
for (final ContextResource resource : naming.findResources()) {
final String name = resource.getName();
removeRef(webApp, name);
}
for (final ContextResourceLink resourceLink : naming.findResourceLinks()) {
final String name = resourceLink.getName();
removeRef(webApp, name);
}
// remove all env entries from the web xml that are not overridable
for (final ContextEnvironment environment : naming.findEnvironments()) {
if (!environment.getOverride()) {
// overrides are not allowed
webApp.getEnvEntryMap().remove(environment.getName());
}
}
}
/**
* Remove jndi references from related info map.
*
* @param webApp web application instance
* @param name jndi reference name
*/
private void removeRef(final WebApp webApp, final String name) {
webApp.getEnvEntryMap().remove(name);
webApp.getEjbRefMap().remove(name);
webApp.getEjbLocalRefMap().remove(name);
webApp.getMessageDestinationRefMap().remove(name);
webApp.getPersistenceContextRefMap().remove(name);
webApp.getPersistenceUnitRefMap().remove(name);
webApp.getResourceRefMap().remove(name);
webApp.getResourceEnvRefMap().remove(name);
}
/**
* Binds given object into given component context.
*
* @param comp context
* @param name name of the binding
* @param value binded object
*/
private void safeBind(final Context comp, final String name, final Object value) {
try {
comp.lookup(name);
LOGGER.debug(name + " already bound, ignoring");
} catch (final Exception e) {
try {
comp.bind(name, value);
} catch (final NamingException ne) {
LOGGER.error("Error in safeBind method", e);
}
}
}
/**
* Gets openejb assembler instance.
*
* @return assembler
*/
private Assembler getAssembler() {
if (assembler == null) {
assembler = (Assembler) SystemInstance.get().getComponent(org.apache.openejb.spi.Assembler.class);
}
return assembler;
}
/**
* Gets container system for openejb.
*
* @return openejb container system
*/
private CoreContainerSystem getContainerSystem() {
if (containerSystem == null) {
containerSystem = (CoreContainerSystem) SystemInstance.get().getComponent(ContainerSystem.class);
}
return containerSystem;
}
/**
* Gets id of the context. Context id
* is host name + context root name.
*
* @param standardContext context instance
* @return id of the context
*/
private String getId(final StandardContext standardContext) {
return getId(Contexts.getHostname(standardContext), standardContext.getPath(), standardContext.getWebappVersion());
}
private String getId(final String host, final String context, final String version) {
String contextRoot = context;
if (isRoot(contextRoot)) {
contextRoot = "";
}
if (!contextRoot.startsWith("/")) {
contextRoot = "/" + contextRoot;
}
return (host == null ? hosts.getDefaultHost() : host) + contextRoot + (version == null || version.isEmpty() ? "" : (version.startsWith("##") ? version : "##" + version));
}
/**
* Gets context info for given context.
*
* @param standardContext context
* @return context info
*/
public ContextInfo getContextInfo(final StandardContext standardContext) {
final String id = getId(standardContext);
final ContextInfo value;
synchronized (infos) {
value = infos.get(id);
}
return value;
}
/**
* Gets context info for given web app info.
*
* @return context info
*/
private synchronized ContextInfo getContextInfo(final String webAppHost, final String webAppContextRoot, final String version) {
String host = webAppHost;
if (host == null) {
host = hosts.getDefaultHost();
}
final String id = getId(host, webAppContextRoot, version);
final ContextInfo value;
synchronized (infos) {
value = infos.get(id);
}
return value;
}
/**
* Add new context info.
*
* @param host host name
* @param standardContext context
* @return context info
*/
public ContextInfo addContextInfo(final String host, final StandardContext standardContext) {
String contextRoot = standardContext.getName();
if (!contextRoot.startsWith("/")) {
contextRoot = "/" + contextRoot;
}
final String id = host + contextRoot;
ContextInfo contextInfo;
synchronized (infos) {
contextInfo = infos.get(id);
if (contextInfo == null) {
contextInfo = new ContextInfo();
final String webappVersion = standardContext.getWebappVersion();
contextInfo.version = webappVersion != null && !webappVersion.isEmpty() ? "##" + webappVersion : webappVersion;
contextInfo.standardContext = standardContext;
infos.put(id, contextInfo);
}
}
return contextInfo;
}
/**
* Removes context info from map.
*
* @param standardContext context
*/
private void removeContextInfo(final StandardContext standardContext) {
boolean found = false;
synchronized (infos) {
final Iterator<Map.Entry<String, ContextInfo>> info = infos.entrySet().iterator();
while (info.hasNext()) {
if (info.next().getValue().standardContext == standardContext) {
info.remove();
found = true;
break;
}
}
}
if (!found) { // unlikely
final String id = getId(standardContext);
synchronized (infos) {
infos.remove(id);
}
}
}
public static class ContextInfo {
public AppInfo appInfo;
public StandardContext standardContext;
public HostConfig deployer;
public Host host;
public EjbModule module; // just during startup
public String version;
public Collection<String> resourceNames = Collections.emptyList();
@Override
public String toString() {
return "ContextInfo{"
+ "appInfo = " + appInfo + ", "
+ "deployer = " + deployer + ", "
+ "host = " + host
+ "}";
}
}
private static class DeployedApplication {
private final AppInfo appInfo;
private final Map<File, Long> watchedResource = new HashMap<>();
public DeployedApplication(final File base, final AppInfo appInfo) {
this.appInfo = appInfo;
watchedResource.put(base, base.lastModified());
if (appInfo != null) {
for (final String resource : appInfo.watchedResources) {
final File file = new File(resource);
watchedResource.put(file, file.lastModified());
}
for (final EjbJarInfo info : appInfo.ejbJars) {
for (final String resource : info.watchedResources) {
final File file = new File(resource);
watchedResource.put(file, file.lastModified());
}
}
for (final WebAppInfo info : appInfo.webApps) {
for (final String resource : info.watchedResources) {
final File file = new File(resource);
watchedResource.put(file, file.lastModified());
}
}
for (final ConnectorInfo info : appInfo.connectors) {
for (final String resource : info.watchedResources) {
final File file = new File(resource);
watchedResource.put(file, file.lastModified());
}
}
}
}
public boolean isModified() {
for (final Map.Entry<File, Long> entry : watchedResource.entrySet()) {
final File file = entry.getKey();
final long lastModified = entry.getValue();
if ((!file.exists() && lastModified != 0L)
|| (file.lastModified() != lastModified)) {
return true;
}
}
return false;
}
}
@Override
public Map<ClassLoader, Map<String, Set<String>>> getJsfClasses() {
return jsfClasses;
}
@Override
public ClassLoader getParentClassLoader(final ClassLoader fallback) {
return (null != this.parentClassLoader ? this.parentClassLoader : fallback);
}
public Map<String, Realm> getRealms() {
return realms;
}
public Map<ClassLoader, InstanceManager> getInstanceManagers() {
return instanceManagers;
}
}