/* * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.container; import org.exoplatform.commons.utils.PrivilegedFileHelper; import org.exoplatform.commons.utils.PropertyManager; import org.exoplatform.commons.utils.SecurityHelper; import org.exoplatform.container.ar.Archive; import org.exoplatform.container.configuration.ConfigurationManager; import org.exoplatform.container.configuration.ConfigurationManagerImpl; import org.exoplatform.container.definition.PortalContainerConfig; import org.exoplatform.container.definition.PortalContainerDefinition; import org.exoplatform.container.monitor.jvm.J2EEServerInfo; import org.exoplatform.container.monitor.jvm.OperatingSystemInfo; import org.exoplatform.container.security.ContainerPermissions; import org.exoplatform.container.spi.ContainerException; import org.exoplatform.container.util.ContainerUtil; import org.exoplatform.container.xml.Configuration; import org.exoplatform.management.annotations.Managed; import org.exoplatform.management.annotations.ManagedDescription; import org.exoplatform.management.jmx.annotations.NameTemplate; import org.exoplatform.management.jmx.annotations.Property; import org.exoplatform.management.rest.annotations.RESTEndpoint; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.test.MockConfigurationManagerImpl; import org.exoplatform.test.MockServletContext; import org.gatein.wci.ServletContainerFactory; import org.gatein.wci.WebAppEvent; import org.gatein.wci.WebAppLifeCycleEvent; import org.gatein.wci.WebAppListener; import org.gatein.wci.authentication.AuthenticationEvent; import org.gatein.wci.authentication.AuthenticationListener; import java.io.File; import java.lang.ref.WeakReference; import java.net.URL; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; /** * Created by The eXo Platform SAS<br> * A base container which plays an important role during the startup. However, * it is recommended that it should not be used directly. * * @author <a href="mailto:tuan08@users.sourceforge.net">Tuan Nguyen</a> * @LevelAPI Provisional */ @Managed @NameTemplate(@Property(key = "container", value = "root")) @RESTEndpoint(path = "rcontainer") public class RootContainer extends ExoContainer implements WebAppListener, AuthenticationListener, TopExoContainer { /** * Serial Version UID */ private static final long serialVersionUID = 812448359436635438L; /** * The name of the attribute used to mark a session as to be invalidated */ public static final String SESSION_TO_BE_INVALIDATED_ATTRIBUTE_NAME = RootContainer.class.getName() + "_TO_BE_INVALIDATED"; /** The field is volatile to properly implement the double checked locking pattern. */ private static volatile RootContainer singleton_; private OperatingSystemInfo osenv_; private PortalContainerConfig config_; private static final Log LOG = ExoLogger.getLogger("exo.kernel.container.RootContainer"); private static final AtomicBoolean booting = new AtomicBoolean(); private final J2EEServerInfo serverenv_ = new J2EEServerInfo(true); private final Set<String> profiles; private final Thread hook = new ShutdownThread(this); private final AtomicBoolean reloading = new AtomicBoolean(); private final AtomicLong lastUpdateTime = new AtomicLong(); /** * The listeners */ private final List<TopExoContainerListener> listeners = new CopyOnWriteArrayList<TopExoContainerListener>(); private ClassLoader loadingCL; private Properties loadingSystemProperties; private volatile Thread reloadingThread; /** * The list of all the tasks to execute while initializing the corresponding portal containers */ private ConcurrentMap<String, ConcurrentMap<String, Queue<PortalContainerInitTaskContext>>> initTasks = new ConcurrentHashMap<String, ConcurrentMap<String, Queue<PortalContainerInitTaskContext>>>(); /** * The list of the web application contexts corresponding to all the portal containers */ private Set<WebAppInitContext> portalContexts = new CopyOnWriteArraySet<WebAppInitContext>(); /** * The list of the all the existing sessions, this will be used in developing mode only */ private final Set<WeakHttpSession> sessions = new CopyOnWriteArraySet<WeakHttpSession>(); /** * The list of the all the portal containers to reload */ private final Set<String> portalContainer2Reload = new CopyOnWriteArraySet<String>(); public RootContainer() { Set<String> profiles = new HashSet<String>(); // Add the profile defined by the server name String envProfile = serverenv_.getServerName(); if (envProfile != null) { profiles.add(envProfile); } // Obtain profile list by runtime properties profiles.addAll(ExoContainer.getProfiles()); // Lof the active profiles LOG.info("Active profiles " + profiles); // SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { Runtime.getRuntime().addShutdownHook(hook); return null; } }); this.profiles = profiles; SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { registerComponentInstance(J2EEServerInfo.class, serverenv_); if (PropertyManager.isDevelopping()) { loadingCL = Thread.currentThread().getContextClassLoader(); loadingSystemProperties = (Properties)System.getProperties().clone(); } return null; } }); } public OperatingSystemInfo getOSEnvironment() { if (osenv_ == null) { osenv_ = (OperatingSystemInfo)this.getComponentInstanceOfType(OperatingSystemInfo.class); } return osenv_; } /** * @return the {@link PortalContainerConfig} corresponding to the {@link RootContainer} */ PortalContainerConfig getPortalContainerConfig() { if (config_ == null) { config_ = (PortalContainerConfig)this.getComponentInstanceOfType(PortalContainerConfig.class); } return config_; } /** * Indicates if the current instance is aware of the {@link PortalContainerConfig} * @return <code>true</code> if we are using the old way to configure the portal containers, * <code>false</code> otherwise */ public boolean isPortalContainerConfigAware() { return getPortalContainerConfig().hasDefinition(); } public J2EEServerInfo getServerEnvironment() { return serverenv_; } /** * {@inheritDoc} */ @Override public Object getComponentInstance(Object componentKey) throws ContainerException { if (reloading.get()) { // To prevent any early access to the portal container synchronized(RootContainer.class) {} } return super.getComponentInstance(componentKey); } public PortalContainer getPortalContainer(final String name) { PortalContainer pcontainer = getComponentInstance(name, PortalContainer.class); if (pcontainer == null) { J2EEServerInfo senv = getServerEnvironment(); if ("standalone".equals(senv.getServerName()) || "test".equals(senv.getServerName())) { try { MockServletContext scontext = new MockServletContext(name); pcontainer = new PortalContainer(this, scontext); final PortalContainer currentPortalContainer = pcontainer; SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { PortalContainer.setInstance(currentPortalContainer); return null; } }); final ConfigurationManagerImpl cService = new MockConfigurationManagerImpl(scontext); cService.addConfiguration(ContainerUtil.getConfigurationURL("conf/portal/configuration.xml")); cService.addConfiguration(ContainerUtil.getConfigurationURL("conf/portal/test-configuration.xml")); cService.processRemoveConfiguration(); SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { currentPortalContainer.registerComponentInstance(ConfigurationManager.class, cService); registerComponentInstance(name, currentPortalContainer); currentPortalContainer.start(true); onStartupComplete(); return null; } }); } catch (Exception ex) { LOG.error(ex.getLocalizedMessage(), ex); } } } return pcontainer; } /** * Register a new portal container. It will try to detect if {@link PortalContainerDefinition} has * been defined, if so it will create the portal container later otherwise we assume that we * expect the old behavior, thus the portal container will be initialized synchronously * @param context the context of the portal container */ public void registerPortalContainer(ServletContext context) { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION); PortalContainerConfig config = getPortalContainerConfig(); if (config.hasDefinition()) { // The new behavior has been detected thus, the creation will be done at the end asynchronously if (config.isPortalContainerName(ContainerUtil.getServletContextName(context))) { // The portal context has been registered has a portal container portalContexts.add(new WebAppInitContext(context)); } else { if (PropertyManager.isDevelopping()) { LOG.info("We assume that the ServletContext '" + ContainerUtil.getServletContextName(context) + "' is not a portal since no portal container definition with the same name has been" + " registered to the component PortalContainerConfig. The related portal container" + " will be declared as disabled."); } config.disablePortalContainer(ContainerUtil.getServletContextName(context)); } // We assume that a ServletContext of a portal container owns configuration files PortalContainer.addInitTask(context, new PortalContainer.RegisterTask()); } else { // Ensure that the portal container has been registered config.registerPortalContainerName(ContainerUtil.getServletContextName(context)); // The old behavior has been detected thus, the creation will be done synchronously createPortalContainer(context); } } /** * Creates all the portal containers that have been registered thanks to the method * <code>registerPortalContainer</code> */ public synchronized void createPortalContainers() { // Keep the old ClassLoader final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); boolean hasChanged = false; try { for (Iterator<WebAppInitContext> it = portalContexts.iterator();it.hasNext();) { WebAppInitContext context = it.next(); // Set the context classloader of the related web application Thread.currentThread().setContextClassLoader(context.getWebappClassLoader()); hasChanged = true; createPortalContainer(context.getServletContext()); } } finally { if (hasChanged) { // Re-set the old classloader Thread.currentThread().setContextClassLoader(currentClassLoader); } } if (PropertyManager.isDevelopping()) { ServletContainerFactory.getServletContainer().addWebAppListener(this); ServletContainerFactory.getServletContainer().addAuthenticationListener(this); checkDependencies(); } else { PortalContainerConfig config = getPortalContainerConfig(); for (String portalContainerName : initTasks.keySet()) { if (config.isPortalContainerName(portalContainerName)) { // Unregister name of portal container that doesn't exist LOG.warn("The portal container '" + portalContainerName + "' doesn't exist or" + " it has not yet been registered, please check your PortalContainerDefinitions and " + "the loading order."); config.unregisterPortalContainerName(portalContainerName); } } // remove all the registered web application contexts // corresponding to the portal containers portalContexts.clear(); // remove all the unneeded tasks initTasks.clear(); } onStartupComplete(); } /** * Checks the dependencies of all the portal containers to make sure that they really exist, if not it will print a warning. */ private void checkDependencies() { PortalContainerConfig config = getPortalContainerConfig(); if (!config.hasDefinition()) { // No definition has been defined so we cannot check return; } final Set<String> existingWebApp = new HashSet<String>(); WebAppListener listener = new WebAppListener() { public void onEvent(WebAppEvent event) { if (event instanceof WebAppLifeCycleEvent && ((WebAppLifeCycleEvent)event).getType() == WebAppLifeCycleEvent.ADDED) { existingWebApp.add(ContainerUtil.getServletContextName(event.getWebApp().getServletContext())); } } }; try { // Collect the servlet context name of all the existing web apps. ServletContainerFactory.getServletContainer().addWebAppListener(listener); // Iterate over all the known portal containers for (Iterator<WebAppInitContext> it = portalContexts.iterator(); it.hasNext();) { WebAppInitContext context = it.next(); String portalContainer = context.getServletContextName(); List<String> dependencies = config.getDependencies(portalContainer); // Check if all the dependencies of the portal container exist. for (String dep : dependencies) { if (!existingWebApp.contains(dep)) { LOG.warn( "The web app '{}' has been defined as a dependency of the portal container '{}' but it doesn't exist.", dep, portalContainer); } } } } finally { ServletContainerFactory.getServletContainer().removeWebAppListener(listener); } } /** * {@inheritDoc} */ public void onEvent(WebAppEvent event) { if (event instanceof WebAppLifeCycleEvent && !stopping.get()) { WebAppLifeCycleEvent waEvent = (WebAppLifeCycleEvent)event; if (waEvent.getType() == WebAppLifeCycleEvent.REMOVED) { String contextName = ContainerUtil.getServletContextName(event.getWebApp().getServletContext()); boolean updated = false; for (Entry<String, ConcurrentMap<String, Queue<PortalContainerInitTaskContext>>> entry : initTasks.entrySet()) { String portalContainer = entry.getKey(); ConcurrentMap<String, Queue<PortalContainerInitTaskContext>> queues = entry.getValue(); for (Queue<PortalContainerInitTaskContext> queue : queues.values()) { for (Iterator<PortalContainerInitTaskContext> it = queue.iterator(); it.hasNext();) { PortalContainerInitTaskContext context = it.next(); if (context.getServletContextName().equals(contextName)) { it.remove(); updated = true; if (portalContainer2Reload.contains(portalContainer)) { // The current portal container is already part of the portal containers to be reloaded to so need to continue // at that point. lastUpdateTime.set(System.currentTimeMillis()); continue; } final PortalContainer container = getPortalContainer(portalContainer); if (container != null) { Set<WebAppInitContext> contexts = container.getWebAppInitContexts(); for (WebAppInitContext webappctx : contexts) { ServletContext ctx = webappctx.getServletContext(); if (context.getServletContextName().equals(ContainerUtil.getServletContextName(ctx))) { // We found a matching servlet context which means that the current servlet context is defined as a // PortalContainerConfigOwner portalContainer2Reload.add(portalContainer); lastUpdateTime.set(System.currentTimeMillis()); // We already found the corresponding servlet context no need to continue break; } } } } } } } if (updated) { LOG.info("The webapp '" + contextName + "' has been undeployed, the related init tasks have been removed"); } } else if (waEvent.getType() == WebAppLifeCycleEvent.ADDED && lastUpdateTime.get() > 0 && reloadingThread == null) { // Reloading thread used to reload asynchronously the containers reloadingThread = new Thread("Reloading") { @Override public void run() { // We delay the reloading to ensure that there is no other webapp that will be reloaded long pause = 5000; do { try { sleep(500); } catch (InterruptedException e) { interrupted(); } } while (System.currentTimeMillis() < lastUpdateTime.get() + pause); dynamicReload(); // We reset the value of the thread to allow other reloading reloadingThread = null; } }; reloadingThread.setDaemon(true); reloadingThread.start(); } } } private void dynamicReload() { final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); final Properties currentSystemProperties = System.getProperties(); boolean hasChanged = false; Configuration newConfig = null; try { Thread.currentThread().setContextClassLoader(loadingCL); hasChanged = true; System.setProperties(loadingSystemProperties); ConfigurationManager cm = loadConfigurationManager(this, false); if (cm != null) { newConfig = cm.getConfiguration(); } } catch (Exception e) { if (LOG.isDebugEnabled()) { LOG.debug("Could not load the new configuration of the root container", e); } } finally { if (hasChanged) { Thread.currentThread().setContextClassLoader(currentClassLoader); System.setProperties(currentSystemProperties); } } if (newConfig == null) { // We have no way to know if the configuration of the root container has changed so // we reload everything LOG.info("The new configuration of the root container could not be loaded," + " thus everything will be reloaded"); reload(); return; } Configuration currentConfig = getConfiguration(); if (currentConfig == null) { // We have no way to know if the configuration of the root container has changed so // we reload everything LOG.info("The current configuration of the root container could not be loaded," + " thus everything will be reloaded"); reload(); return; } if (newConfig.getCurrentSize() != currentConfig.getCurrentSize() || newConfig.getCurrentHash() != currentConfig.getCurrentHash()) { // The root container has changed so we reload everything LOG.info("The configuration of the root container has changed," + " thus everything will be reloaded"); reload(); return; } LOG.info("The configuration of the root container did not change," + " thus only affected portal containers will be reloaded"); for (String pc : portalContainer2Reload) { // At least one dependency has changed so we reload all the affected portal containers reload(pc); } onStartupComplete(); } /** * Adds a session attribute indicating that the sessions have to be invalidated */ private void markSessionsAsToBeInvalidated(String portalContainerName) { for (WeakHttpSession wSess : sessions) { HttpSession sess = wSess.get(); if (sess == null) { continue; } else if (portalContainerName == null || portalContainerName.equals(ContainerUtil.getServletContextName(sess.getServletContext()))) { try { sess.setAttribute(SESSION_TO_BE_INVALIDATED_ATTRIBUTE_NAME, Boolean.TRUE); } catch (IllegalStateException e) { if (LOG.isDebugEnabled()) { LOG.debug("Could not set the flag indicating that the session must be invalidated", e); } } } } sessions.clear(); } /** * {@inheritDoc} */ public void onEvent(AuthenticationEvent evt) { HttpSession sess = evt.getRequest().getSession(false); if (sess == null || !getPortalContainerConfig().isPortalContainerName(ContainerUtil.getServletContextName(sess.getServletContext()))) return; switch (evt.getType()) { case LOGIN : { cleanupSessions(); sessions.add(new WeakHttpSession(sess)); break; } default: { } } } /** * This is used to lazy-cleanup the set of sessions to prevent memory leak. This is needed since * WCI doesn't provide anymore the logout event. */ private void cleanupSessions() { Set<WeakHttpSession> sessionsToBeRemoved = new HashSet<WeakHttpSession>(); for (WeakHttpSession wSess : sessions) { HttpSession sess = wSess.get(); if (sess == null) { sessionsToBeRemoved.add(wSess); } } sessions.removeAll(sessionsToBeRemoved); } /** * Create the portal container * @param context the servlet context */ public synchronized void createPortalContainer(ServletContext context) { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION); // Keep the old ClassLoader final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); boolean hasChanged = false; final String portalContainerName = ContainerUtil.getServletContextName(context); try { LOG.info("Trying to create the portal container '" + portalContainerName + "'"); PortalContainer pcontainer = new PortalContainer(this, context); PortalContainer.setInstance(pcontainer); executeInitTasks(pcontainer, PortalContainerPreInitTask.TYPE); // Set the full classloader of the portal container Thread.currentThread().setContextClassLoader(pcontainer.getPortalClassLoader()); hasChanged = true; ConfigurationManagerImpl cService = new ConfigurationManagerImpl(pcontainer.getPortalContext(), profiles); if (ConfigurationManager.LOG_DEBUG) { showDependencies(portalContainerName); } // add configs from services try { cService.addConfiguration(ContainerUtil.getConfigurationURL("conf/portal/configuration.xml")); } catch (Exception ex) { LOG.error("Cannot add configuration conf/portal/configuration.xml. ServletContext: " + context, ex); } // Add configuration that depends on the environment String uri; if (serverenv_.isJBoss()) { uri = "conf/portal/jboss-configuration.xml"; } else { uri = "conf/portal/generic-configuration.xml"; } Collection<URL> envConf = ContainerUtil.getConfigurationURL(uri); try { cService.addConfiguration(envConf); } catch (Exception ex) { LOG.error("Cannot add configuration " + uri + ". ServletContext: " + context, ex); } // add configs from web apps Set<WebAppInitContext> contexts = pcontainer.getWebAppInitContexts(); for (WebAppInitContext webappctx : contexts) { ServletContext ctx = webappctx.getServletContext(); try { cService.addConfiguration(ctx, "war:/conf/configuration.xml"); } catch (Exception ex) { LOG.error("Cannot add configuration war:/conf/configuration.xml. ServletContext: " + ctx, ex); } } // add config from application server, // $AH_HOME/exo-conf/portal/configuration.xml String overrideConfig = getServerEnvironment().getExoConfigurationDirectory() + "/portal/" + portalContainerName + "/configuration.xml"; try { File file = new File(overrideConfig); if (file.exists()) cService.addConfiguration(file.toURI().toURL()); } catch (Exception ex) { LOG.error("Cannot add configuration " + overrideConfig + ". ServletContext: " + context, ex); } cService.processRemoveConfiguration(); pcontainer.registerComponentInstance(ConfigurationManager.class, cService); registerComponentInstance(portalContainerName, pcontainer); pcontainer.start(true); // Register the portal as an mbean getManagementContext().register(pcontainer); // executeInitTasks(pcontainer, PortalContainerPostInitTask.TYPE); executeInitTasks(pcontainer, PortalContainerPostCreateTask.TYPE); LOG.info("The portal container '" + portalContainerName + "' has been created successfully"); } catch (Exception ex) { LOG.error("Cannot create the portal container '" + portalContainerName + "' . ServletContext: " + context, ex); } finally { if (hasChanged) { // Re-set the old classloader Thread.currentThread().setContextClassLoader(currentClassLoader); } try { PortalContainer.setInstance(null); } catch (Exception e) { LOG.warn("An error occured while cleaning the ThreadLocal", e); } } } /** * Prints the list of dependencies of a given portal container * @param portalContainerName the name of the portal container for which we want the dependencies */ private void showDependencies(final String portalContainerName) { PortalContainerConfig config = getPortalContainerConfig(); List<String> dependencies = config == null ? null : config.getDependencies(portalContainerName); if (dependencies == null || dependencies.isEmpty()) { LOG.info("No dependencies have been defined for the portal container '{}'", portalContainerName); } else { StringBuilder listDep = new StringBuilder(); boolean first = true; for (String dep : dependencies) { if (first) { first = false; } else { listDep.append(", "); } listDep.append(dep); } LOG.info("The dependencies ordered by priority of the portal container '{}' are: {}", portalContainerName, listDep.toString()); } } /** * Removes the portal container * @param servletContext the servlet context */ synchronized public void removePortalContainer(ServletContext servletContext) { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION); this.unregisterComponent(ContainerUtil.getServletContextName(servletContext)); } public static Object getComponent(Class<?> key) { return getInstance().getComponentInstanceOfType(key); } /** * Builds a root container and returns it. * * @return a root container * @throws Error if the root container initialization failed */ private static RootContainer buildRootContainer() { try { return SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<RootContainer>() { public RootContainer run() throws Exception { RootContainer rootContainer = new RootContainer(); ConfigurationManager service = loadConfigurationManager(rootContainer, true); rootContainer.registerComponentInstance(ConfigurationManager.class, service); rootContainer.start(true); return rootContainer; } }); } catch (PrivilegedActionException e) { Exception cause = e.getException(); LOG.error("Could not build root container", cause); LOG.error(e.getLocalizedMessage(), cause); return null; } } /** * @param rootContainer * @return * @throws Exception */ private static ConfigurationManager loadConfigurationManager(RootContainer rootContainer, boolean logEnabled) throws Exception { final ConfigurationManagerImpl service = new ConfigurationManagerImpl(rootContainer.profiles, logEnabled); service.addConfiguration(ContainerUtil.getConfigurationURL("conf/configuration.xml")); if (System.getProperty("maven.exoplatform.dir") != null) { service.addConfiguration(ContainerUtil.getConfigurationURL("conf/test-configuration.xml")); } J2EEServerInfo serverEnv = rootContainer.getServerEnvironment(); service.addConfiguration(Archive.getConfigurationURL(serverEnv.getApplicationDeployDirectories(), serverEnv.getApplicationDeployArchives(), "META-INF/exo-conf/configuration.xml")); String confDir = serverEnv.getExoConfigurationDirectory(); String overrideConf = confDir + "/configuration.xml"; File file = new File(overrideConf); if (PrivilegedFileHelper.exists(file)) { service.addConfiguration("file:" + overrideConf); } service.processRemoveConfiguration(); if (PropertyManager.isDevelopping()) { Configuration conf = service.getConfiguration(); if (conf != null) { conf.keepCurrentState(); } } return service; } /** * Get the unique instance of the root container per VM. The implementation relies on the double * checked locking pattern to guarantee that only one instance will be initialized. See * * @return the root container singleton */ public static RootContainer getInstance() { RootContainer result = singleton_; if (result == null) { synchronized (RootContainer.class) { result = singleton_; if (result == null) { if (booting.get()) { throw new IllegalStateException("Already booting by the same thread"); } else { booting.set(true); try { LOG.info("Building root container"); long time = -System.currentTimeMillis(); result = buildRootContainer(); if (result != null) { time += System.currentTimeMillis(); LOG.info("Root container is built (build time " + time + "ms)"); singleton_ = result; SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { ExoContainerContext.setTopContainer(singleton_); return null; } }); LOG.info("Root container booted"); } else { LOG.error("Failed to boot root container"); } } finally { booting.set(false); } } } } } return result; } /** * Set the unique instance of the root container * @param rcontainer the root container */ static public void setInstance(RootContainer rcontainer) { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION); singleton_ = rcontainer; } /** * @return the configuration of the container in XML format */ @Managed @ManagedDescription("The configuration of the container in XML format.") public String getConfigurationXML() { Configuration config = getConfiguration(); if (config == null) { LOG.warn("The configuration of the RootContainer could not be found"); return null; } return config.toXML(); } /** * Makes the RootContainer reloads itself and all the portal containers */ @Managed @ManagedDescription("Make the RootContainer reloads itself and all the portal containers.") public void reload() { if (!PropertyManager.isDevelopping()) { LOG.debug("The containers can be reloaded only in developping mode, " + "please set the system property 'exo.product.developing' to 'true'"); return; } else if (stopping.get()) { LOG.debug("The containers cannot be reloaded as we are currently stopping the root container."); return; } try { long time = System.currentTimeMillis(); LOG.info("Trying to reload all the containers"); LOG.info("Trying to stop all the containers"); stop(); markSessionsAsToBeInvalidated(null); synchronized (RootContainer.class) { // Make early accesses to the root container to wait singleton_ = null; LOG.info("All the containers have been stopped successfully"); // We unregister the root container SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { Runtime.getRuntime().removeShutdownHook(hook); return null; } }); ServletContainerFactory.getServletContainer().removeWebAppListener(this); ServletContainerFactory.getServletContainer().removeAuthenticationlistener(this); LOG.info("Trying to restart the root container"); RootContainer rootContainer = null; final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); boolean hasChanged = false; try { Thread.currentThread().setContextClassLoader(loadingCL); hasChanged = true; System.setProperties(loadingSystemProperties); rootContainer = RootContainer.getInstance(); rootContainer.reloading.set(true); rootContainer.initTasks = initTasks; rootContainer.portalContexts = portalContexts; PortalContainer.reloadConfig(); LOG.info("Trying to restart all the portal containers"); rootContainer.createPortalContainers(); } finally { if (rootContainer != null) { rootContainer.reloading.set(false); } if (hasChanged) { Thread.currentThread().setContextClassLoader(currentClassLoader); } } } LOG.info("All the containers have been reloaded successfully in " + (System.currentTimeMillis() - time) + " ms"); } catch (Exception e) { LOG.error("Could not reload the containers", e); } } /** * Makes the RootContainer reloads only a given portal container */ @Managed @ManagedDescription("Make the RootContainer reloads only a given portal container.") public void reload(String portalContainerName) { if (!PropertyManager.isDevelopping()) { LOG.debug("The portal container '" + portalContainerName + "' can be reloaded only in developping mode, please set the system property 'exo.product.developing' to 'true'"); return; } else if (stopping.get()) { LOG.debug("The portal container '" + portalContainerName + "' cannot be reloaded as we are currently stopping the root container."); return; } try { long time = System.currentTimeMillis(); LOG.info("Trying to reload the portal container '" + portalContainerName + "'"); PortalContainer pc = getPortalContainer(portalContainerName); if (pc == null) { throw new IllegalArgumentException("The portal container '" + portalContainerName + "' doesn't exists or has not yet been created"); } LOG.info("Trying to stop the portal container '" + portalContainerName + "'"); pc.stop(); markSessionsAsToBeInvalidated(portalContainerName); synchronized (RootContainer.class) { LOG.info("The portal container '" + portalContainerName + "' has been stopped successfully"); final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); boolean hasChanged = false; try { reloading.set(true); // Make early accesses to the root container to wait unregisterComponent(portalContainerName); LOG.info("Trying to restart the portal container '" + portalContainerName + "'"); for (Iterator<WebAppInitContext> it = portalContexts.iterator();it.hasNext();) { WebAppInitContext context = it.next(); if (context.getServletContextName().equals(portalContainerName)) { // Set the context classloader of the related web application Thread.currentThread().setContextClassLoader(context.getWebappClassLoader()); hasChanged = true; createPortalContainer(context.getServletContext()); break; } } } finally { if (hasChanged) { // Re-set the old classloader Thread.currentThread().setContextClassLoader(currentClassLoader); } reloading.set(false); } } LOG.info("The portal container '" + portalContainerName + "' has been reloaded in " + (System.currentTimeMillis() - time) + " ms"); } catch (Exception e) { LOG.error("Could not reload the portal container '" + portalContainerName + "'", e); } } /** * Calls the other method <code>addInitTask</code> with <code>ServletContext.getServletContextName()</code> * as portal container name * * @param context the servlet context from which the task comes from * @param task the task to add */ public void addInitTask(ServletContext context, PortalContainerInitTask task) { addInitTask(context, task, ContainerUtil.getServletContextName(context)); } /** * First check if the related portal container has already been initialized. If so * it will call the method onAlreadyExists on the given task otherwise the task will * be added to the task list to execute during the related portal container initialization * * @param context the servlet context from which the task comes from * @param task the task to add * @param portalContainer the name of the portal container on which the task must be executed */ public void addInitTask(ServletContext context, PortalContainerInitTask task, String portalContainer) { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION); final PortalContainer container = getPortalContainer(portalContainer); if (!task.alreadyExists(container) || lastUpdateTime.get() > 0) { if (LOG.isDebugEnabled()) LOG.debug("The portal container '" + portalContainer + "' has not yet been initialized, thus the task can be added"); addInitTaskToQueue(context, task, portalContainer); } else { if (LOG.isDebugEnabled()) LOG.debug("The portal container '" + portalContainer + "' has already been initialized, thus we call onAlreadyExists"); PortalContainer oldPortalContainer = PortalContainer.getInstanceIfPresent(); try { PortalContainer.setInstance(container); task.onAlreadyExists(context, container); } finally { PortalContainer.setInstance(oldPortalContainer); } if (PropertyManager.isDevelopping()) { // To be able to reload the tasks properly, we need to add the task in the queue of tasks addInitTaskToQueue(context, task, portalContainer); } } } /** * Adds the task to the queue of tasks * * @param context the servlet context from which the task comes from * @param task the task to add * @param portalContainer the name of the portal container on which the task must be executed */ private void addInitTaskToQueue(ServletContext context, PortalContainerInitTask task, String portalContainer) { ConcurrentMap<String, Queue<PortalContainerInitTaskContext>> queues = initTasks.get(portalContainer); if (queues == null) { queues = new ConcurrentHashMap<String, Queue<PortalContainerInitTaskContext>>(); final ConcurrentMap<String, Queue<PortalContainerInitTaskContext>> q = initTasks.putIfAbsent(portalContainer, queues); if (q != null) { queues = q; } } final String type = task.getType(); Queue<PortalContainerInitTaskContext> queue = queues.get(type); if (queue == null) { final List<String> dependencies = getPortalContainerConfig().getDependencies(portalContainer); if (dependencies == null || dependencies.isEmpty()) { // No order is required queue = new ConcurrentLinkedQueue<PortalContainerInitTaskContext>(); } else { queue = new PriorityBlockingQueue<PortalContainerInitTaskContext>(10, new PortalContainerInitTaskContextComparator(dependencies)); } final Queue<PortalContainerInitTaskContext> q = queues.putIfAbsent(type, queue); if (q != null) { queue = q; } } else if (reloading.get()) { // The queue already exists and we are in reloading phase, we will then check // if a task of the same type exist for the same servlet context if so we replace it // with a new one String contextName = ContainerUtil.getServletContextName(context); Class<?> c = task.getClass(); for (Iterator<PortalContainerInitTaskContext> it = queue.iterator(); it.hasNext();) { PortalContainerInitTaskContext ctx = it.next(); if (ctx.getServletContextName().equals(contextName) && ctx.getTask().getClass().equals(c)) { it.remove(); break; } } } queue.add(new PortalContainerInitTaskContext(context, task)); } /** * Executes all the tasks of the given type related to the given portal container * @param portalContainer the portal container on which we want to execute the tasks * @param type the type of the task to execute */ private void executeInitTasks(PortalContainer portalContainer, String type) { final String portalContainerName = portalContainer.getName(); final ConcurrentMap<String, Queue<PortalContainerInitTaskContext>> queues = initTasks.get(portalContainerName); if (queues == null) { return; } final Queue<PortalContainerInitTaskContext> queue = queues.get(type); if (queue == null) { return; } if (LOG.isDebugEnabled()) LOG.debug("Start launching the " + type + " tasks of the portal container '" + portalContainer + "'"); // Keep the old ClassLoader final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); PortalContainerInitTaskContext context; boolean hasChanged = false; Collection<PortalContainerInitTaskContext> bckCollection = null; if (PropertyManager.isDevelopping()) { bckCollection = new ArrayList<PortalContainerInitTaskContext>(queue); } try { while ((context = queue.poll()) != null) { // Set the context classloader of the related web application Thread.currentThread().setContextClassLoader(context.getWebappClassLoader()); hasChanged = true; context.getTask().execute(context.getServletContext(), portalContainer); } } finally { if (hasChanged) { // Re-set the old classloader Thread.currentThread().setContextClassLoader(currentClassLoader); } } if (PropertyManager.isDevelopping()) { // reload the queue, it is required because PriorityBlockingQueue only guarantee the order // when we use the method poll if we try to use the iterator the priority is not respected queue.addAll(bckCollection); } else { // Clear the queue of task queue.clear(); // Remove this specific type of PortalContainerInitTaskContext from the existing queues queues.remove(type); if (queues.isEmpty()) { // If there is no queue anymore remove init tasks holder for this portal container initTasks.remove(portalContainerName); } } if (LOG.isDebugEnabled()) LOG.debug("End launching the " + type + " tasks of the portal container '" + portalContainer + "'"); } /** * Calls all the listeners to indicate that the startup is complete */ private void onStartupComplete() { if (!listeners.isEmpty()) { for (TopExoContainerListener listener : listeners) { listener.onStartupComplete(); } } } /** * {@inheritDoc} */ public void addListener(TopExoContainerListener listener) { listeners.add(listener); } static class ShutdownThread extends Thread { RootContainer container_; ShutdownThread(RootContainer container) { container_ = container; } @Override public void run() { SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>() { public Void run() { container_.stop(); return null; } }); } } @Override public void stop() { super.stop(); ExoContainerContext.setTopContainer(null); } /** * This interface is used to define a task that needs to be launched at a given state during the * initialization of a portal container */ public static interface PortalContainerInitTask { /** * This method allows the implementation to define what the state "already exists" * means for a portal container * * @param portalContainer the value of the current portal container * @return <code>true</code> if the portal container exists according to the task * requirements, <code>false</code> otherwise */ public boolean alreadyExists(PortalContainer portalContainer); /** * This method is called if the related portal container has already been registered * * @param context the servlet context of the web application * @param portalContainer the value of the current portal container */ public void onAlreadyExists(ServletContext context, PortalContainer portalContainer); /** * Executes the task * * @param context the servlet context of the web application * @param portalContainer The portal container on which we would like to execute the task */ public void execute(ServletContext context, PortalContainer portalContainer); /** * @return the type of the task */ public String getType(); } /** * This class is used to define a task that needs to be launched after the initialization of a * portal container */ public static abstract class PortalContainerPostInitTask implements PortalContainerInitTask { /** * The name of the type of task */ public static final String TYPE = "post-init"; /** * {@inheritDoc} */ public final boolean alreadyExists(PortalContainer portalContainer) { return portalContainer != null && portalContainer.isStarted(); } /** * {@inheritDoc} */ public final void onAlreadyExists(ServletContext context, PortalContainer portalContainer) { execute(context, portalContainer); } /** * {@inheritDoc} */ public final String getType() { return TYPE; } } /** * This class is used to define a task that needs to be launched before the initialization of a * portal container */ public static abstract class PortalContainerPreInitTask implements PortalContainerInitTask { /** * The name of the type of task */ public static final String TYPE = "pre-init"; /** * {@inheritDoc} */ public final boolean alreadyExists(PortalContainer portalContainer) { return portalContainer != null; } /** * {@inheritDoc} */ public final void onAlreadyExists(ServletContext context, PortalContainer portalContainer) { if (PropertyManager.isDevelopping()) { execute(context, portalContainer); return; } throw new IllegalStateException("No pre init tasks can be added to the portal container '" + portalContainer.getName() + "', because it has already been " + "initialized. Check the webapp '" + ContainerUtil.getServletContextName(context) + "'"); } /** * {@inheritDoc} */ public final String getType() { return TYPE; } } /** * This class is used to define a task that needs to be launched after creating a portal container * Those type of tasks must be launched after all the "post-init" tasks. */ public static abstract class PortalContainerPostCreateTask implements PortalContainerInitTask { /** * The name of the type of task */ public static final String TYPE = "post-create"; /** * {@inheritDoc} */ public final boolean alreadyExists(PortalContainer portalContainer) { return portalContainer != null && portalContainer.isStarted(); } /** * {@inheritDoc} */ public final void onAlreadyExists(ServletContext context, PortalContainer portalContainer) { execute(context, portalContainer); } /** * {@inheritDoc} */ public final String getType() { return TYPE; } } /** * This class is used to defined the context of the embedded {@link PortalContainerInitTask} */ static class PortalContainerInitTaskContext extends WebAppInitContext { /** * The task to execute */ private final PortalContainerInitTask task; PortalContainerInitTaskContext(ServletContext context, PortalContainerInitTask task) { super(context); this.task = task; } public PortalContainerInitTask getTask() { return task; } } /** * This class is used to compare the {@link PortalContainerInitTaskContext} */ static class PortalContainerInitTaskContextComparator implements Comparator<PortalContainerInitTaskContext> { private final List<String> dependencies; PortalContainerInitTaskContextComparator(List<String> dependencies) { this.dependencies = dependencies; } /** * This will sort all the {@link PortalContainerInitTaskContext} such that we will first have * all the web applications defined in the list of dependencies of the * related portal container (see {@link PortalContainerConfig} for more details * about the dependencies) ordered in the same order as the dependencies, then * we will have all the web applications undefined ordered by context name */ public int compare(PortalContainerInitTaskContext ctx1, PortalContainerInitTaskContext ctx2) { int idx1 = dependencies.indexOf(ctx1.getServletContextName()); int idx2 = dependencies.indexOf(ctx2.getServletContextName()); if (idx1 == -1 && idx2 != -1) { return 1; } else if (idx1 != -1 && idx2 == -1) { return -1; } else if (idx1 == -1 && idx2 == -1) { return ctx1.getServletContextName().compareTo(ctx2.getServletContextName()); } else { return idx1 - idx2; } } } private static class WeakHttpSession extends WeakReference<HttpSession> { public WeakHttpSession(HttpSession session) { super(session); } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; WeakHttpSession other = (WeakHttpSession)obj; HttpSession session = get(); HttpSession otherSession = other.get(); if (session == null) { return otherSession == null; } else if (otherSession == null) { return false; } return session.getId().equals(otherSession.getId()); } /** * {@inheritDoc} */ @Override public int hashCode() { HttpSession session = get(); return session == null ? 0 : session.getId().hashCode(); } } }