package org.etk.kernel.container; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.servlet.ServletContext; import org.etk.kernel.container.RootContainer.PortalContainerInitTask; import org.etk.kernel.container.definition.ApplicationContainerConfig; import org.etk.kernel.container.jmx.MX4JComponentAdapterFactory; import org.etk.kernel.container.xml.Configuration; import org.etk.kernel.container.xml.PortalContainerInfo; import org.etk.kernel.management.annotations.Managed; import org.etk.kernel.management.annotations.ManagedDescription; import org.etk.kernel.management.jmx.annotations.Property; import org.etk.kernel.management.jmx.annotations.NamingContext; import org.etk.kernel.management.jmx.annotations.NameTemplate; @Managed @NamingContext(@Property(key = "application", value = "{Name}")) @NameTemplate({@Property(key = "container", value = "application"), @Property(key = "name", value = "{Name}")}) public class ApplicationContainer extends KernelContainer implements SessionManagerContainer { /** * Serial Version UID */ private static final long serialVersionUID = -9110532469581690803L; /** * The default name of the portal container */ public static final String DEFAULT_PORTAL_CONTAINER_NAME; /** * The default name of a the {@link ServletContext} of the rest web * application */ public static final String DEFAULT_REST_CONTEXT_NAME; /** * The default name of a the realm */ public static final String DEFAULT_REALM_NAME; /** * The configuration of the portal containers */ private static final ApplicationContainerConfig CONFIG; static { KernelContainer top = KernelContainerContext.getTopContainer(); CONFIG = top instanceof RootContainer ? ((RootContainer) top).getPortalContainerConfig() : null; if (CONFIG == null) { DEFAULT_PORTAL_CONTAINER_NAME = ApplicationContainerConfig.DEFAULT_PORTAL_CONTAINER_NAME; DEFAULT_REST_CONTEXT_NAME = ApplicationContainerConfig.DEFAULT_REST_CONTEXT_NAME; DEFAULT_REALM_NAME = ApplicationContainerConfig.DEFAULT_REALM_NAME; } else { DEFAULT_PORTAL_CONTAINER_NAME = CONFIG.getDefaultPortalContainer(); DEFAULT_REST_CONTEXT_NAME = CONFIG.getDefaultRestContext(); DEFAULT_REALM_NAME = CONFIG.getDefaultRealmName(); } } private volatile boolean started_; private PortalContainerInfo pinfo_; private SessionManager smanager_; /** * The name of the portal container */ private final String name; /** * The comparator used to sort the web applications by priorities */ private final Comparator<WebAppInitContext> webAppComparator; /** * The full {@link ServletContext} of the portal container after merging all * the {@link ServletContext} that have been registered */ private final ServletContext portalMergedContext; /** * The full {@link ClassLoader} of the portal container after merging all * the {@link ClassLoader} of the {@link ServletContext} that have been * registered */ private final ClassLoader portalMergedClassLoader; /** * The {@link Set} of all the web application context that share * configuration */ private volatile Set<WebAppInitContext> webAppContexts; /** * To allow overriding we need to have a custom {@link ClassLoader} by web * applications by portal containers */ private volatile Map<String, ClassLoader> webAppClassLoaders; /** * The {@link ServletContext} of the current portal container */ final ServletContext portalContext; public ApplicationContainer(RootContainer parent, ServletContext portalContext) { super(new MX4JComponentAdapterFactory(), parent); registerComponentInstance(ServletContext.class, portalContext); context.setName(portalContext.getServletContextName()); pinfo_ = new PortalContainerInfo(portalContext); registerComponentInstance(PortalContainerInfo.class, pinfo_); this.name = portalContext.getServletContextName(); final ApplicationContainerConfig config = parent.getPortalContainerConfig(); final List<String> dependencies = config == null ? null : config .getDependencies(name); if (dependencies == null || dependencies.isEmpty()) { // No order is required this.webAppComparator = null; } else { this.webAppComparator = new WebAppInitContextComparator(dependencies); } this.webAppContexts = Collections.singleton(new WebAppInitContext(portalContext)); this.portalContext = portalContext; this.portalMergedContext = new PortalContainerContext(this); this.portalMergedClassLoader = new PortalContainerClassLoader(this); this.webAppClassLoaders = Collections.unmodifiableMap(Collections .singletonMap(name, portalMergedClassLoader)); } /** * @return a {@link Set} ordered by priority of all the * {@link WebAppInitContext} that represents the full portal context */ Set<WebAppInitContext> getWebAppInitContexts() { return webAppContexts; } /** * This gives the merged {@link ClassLoader} between the * {@link PortalContainerClassLoader} and the {@link ClassLoader} of the web * application. * * @param context * the {@link ServletContext} of the web application * @return the merged {@link ClassLoader} between the * {@link PortalContainerClassLoader} and the {@link ClassLoader} of * the web application that allows us to override resources * contained into the {@link ClassLoader} of the web application */ public ClassLoader getWebAppClassLoader(ServletContext context) { final String contextName = context.getServletContextName(); ClassLoader cl = webAppClassLoaders.get(contextName); if (cl == null) { synchronized (this) { cl = webAppClassLoaders.get(contextName); if (cl == null) { cl = new UnifiedClassLoader(new ClassLoader[] { Thread.currentThread().getContextClassLoader(), portalMergedClassLoader }); Map<String, ClassLoader> cls = new HashMap<String, ClassLoader>( webAppClassLoaders); cls.put(contextName, cl); this.webAppClassLoaders = Collections.unmodifiableMap(cls); } } } return cl; } /** * @return the full {@link ClassLoader} of the portal container after * merging all the {@link ClassLoader} of all {@link ServletContext} * that have been registered */ public ClassLoader getPortalClassLoader() { return portalMergedClassLoader; } /** * @return the full {@link ServletContext} of the portal container after * merging all the {@link ServletContext} that have been registered */ public ServletContext getPortalContext() { return portalMergedContext; } /** * Register a new servlet context that contains configuration files and * potentially resource files We assume that this method is called within * the initialization context of the related web application * * @param context * the {@link ServletContext} of the web application to register */ public synchronized void registerContext(ServletContext context) { final WebAppInitContext webappCtx = new WebAppInitContext(context); if (!webAppContexts.contains(webappCtx)) { final Set<WebAppInitContext> contexts; if (webAppComparator == null) { contexts = new HashSet<WebAppInitContext>(webAppContexts); } else { contexts = new TreeSet<WebAppInitContext>(webAppComparator); contexts.addAll(webAppContexts); } contexts.add(webappCtx); this.webAppContexts = Collections.unmodifiableSet(contexts); } } /** * Unregister a servlet context that contains configuration files and * potentially resource files * * @param context * the {@link ServletContext} of the web application to * unregister */ public synchronized void unregisterContext(ServletContext context) { final WebAppInitContext webappCtx = new WebAppInitContext(context); if (webAppContexts.contains(webappCtx)) { final Set<WebAppInitContext> contexts; if (webAppComparator == null) { contexts = new HashSet<WebAppInitContext>(webAppContexts); } else { contexts = new TreeSet<WebAppInitContext>(webAppComparator); contexts.addAll(webAppContexts); } contexts.remove(webappCtx); this.webAppContexts = Collections.unmodifiableSet(contexts); } } @Managed @ManagedDescription("The application container name") public String getName() { return name; } @Managed @ManagedDescription("The configuration of the container in XML format.") public String getConfigurationXML() { Configuration conf = getConfiguration(); if (conf == null) { log.warn("The configuration of the ApplicationContainer could not be found"); return null; } Configuration result = Configuration.merge( ((KernelContainer) parent).getConfiguration(), conf); if (result == null) { log.warn("The configurations could not be merged"); return null; } return result.toXML(); } public SessionContainer createSessionContainer(String id, String owner) { SessionContainer scontainer = getSessionManager().getSessionContainer(id); if (scontainer != null) getSessionManager().removeSessionContainer(id); scontainer = new SessionContainer(id, owner); scontainer.setPortalName(pinfo_.getContainerName()); getSessionManager().addSessionContainer(scontainer); SessionContainer.setInstance(scontainer); return scontainer; } public void removeSessionContainer(String sessionID) { getSessionManager().removeSessionContainer(sessionID); } public List<SessionContainer> getLiveSessions() { return getSessionManager().getLiveSessions(); } public SessionManager getSessionManager() { if (smanager_ == null) smanager_ = (SessionManager) this .getComponentInstanceOfType(SessionManager.class); return smanager_; } public PortalContainerInfo getPortalContainerInfo() { return pinfo_; } /** * @return the current instance of {@link PortalContainer} that has been * stored into the related {@link ThreadLocal}. If no value has been * set the default portal container will be returned */ public static ApplicationContainer getInstance() { ApplicationContainer container = getInstanceIfPresent(); if (container == null) { container = RootContainer.getInstance().getPortalContainer(DEFAULT_PORTAL_CONTAINER_NAME); ApplicationContainer.setInstance(container); } return container; } /** * @return the current instance of {@link ExoContainer} that has been stored * into the {@link ThreadLocal} of {@link ExoContainerContext}. If * no {@link PortalContainer} has been set, it will return * <code>null</code> */ public static ApplicationContainer getInstanceIfPresent() { KernelContainer container = KernelContainerContext.getCurrentContainerIfPresent(); if (container instanceof ApplicationContainer) { return (ApplicationContainer) container; } return null; } /** * @see the method isPortalContainerName of {@link ApplicationContainerConfig} */ public static boolean isPortalContainerName(String name) { if (CONFIG == null) { return DEFAULT_PORTAL_CONTAINER_NAME.equals(name); } else { return CONFIG.isPortalContainerName(name); } } /** * Add an init-task to all the portal container instances related to the * given ServletContext * * @param context * the context from which we extract the context name * @param task * the task to execute */ public static void addInitTask(ServletContext context, PortalContainerInitTask task) { addInitTask(context, task, null); } /** * Add an init-task to all the portal container instances related to the * given ServletContext if the given portal container name is * <code>null</code> other it will execute the task only of this portal * container if the {@link ServletContext} is on of its dependencies * * @param context * the context from which we extract the context name * @param task * the task to execute * @param portalContainerName * the name of the portal container for which we want to execute * the task */ public static void addInitTask(ServletContext context, PortalContainerInitTask task, String portalContainerName) { if (context == null || CONFIG == null) { return; } String contextName = context.getServletContextName(); List<String> portalContainerNames = CONFIG .getPortalContainerNames(contextName); RootContainer root = RootContainer.getInstance(); // We assume that we have at list one portal container otherwise there // is a bug in PortalContainerConfig for (String name : portalContainerNames) { if (portalContainerName == null || portalContainerName.equals(name)) { root.addInitTask(context, task, name); } } } /** * Gives the first portal container instance related to the given * ServletContext * * @param context * the context from which we extract the context name */ public static ApplicationContainer getInstance(ServletContext context) { if (context == null || CONFIG == null) { return null; } String portalContainerName = CONFIG.getPortalContainerName(context.getServletContextName()); RootContainer root = RootContainer.getInstance(); return root.getPortalContainer(portalContainerName); } /** * We first try to get the ExoContainer that has been stored into the * ThreadLocal if the value is of type PortalContainer, we return it * otherwise we get the portal container corresponding the given servlet * context * * @param context * the context from which we extract the portal container name */ public static ApplicationContainer getCurrentInstance(ServletContext context) { final ApplicationContainer container = getInstanceIfPresent(); if (container == null) { return ApplicationContainer.getInstance(context); } return container; } /** * Returns the name of the current portal container that has been stored * into the ThreadLocal. If no value can be found the value of * PortalContainer.DEFAULT_PORTAL_CONTAINER_NAME will be used */ public static String getCurrentPortalContainerName() { final ApplicationContainer container = getInstanceIfPresent(); if (container == null) { return DEFAULT_PORTAL_CONTAINER_NAME; } else { return container.getName(); } } /** * Returns the name of the current rest context corresponding to the portal * container that has been stored into the ThreadLocal. If no value can be * found the value of PortalContainer.DEFAULT_REST_CONTEXT_NAME will be used */ public static String getCurrentRestContextName() { final String containerName = getCurrentPortalContainerName(); return getRestContextName(containerName); } /** * Returns the name of the rest context corresponding to the given portal * container name * * @param portalContainerName * the name of the portal container for which we want the name of * the rest {@link ServletContext} */ public static String getRestContextName(String portalContainerName) { if (CONFIG == null) { return DEFAULT_REST_CONTEXT_NAME; } return CONFIG.getRestContextName(portalContainerName); } /** * Returns the name of the rest context corresponding to the current portal * container */ public String getRestContextName() { return getRestContextName(getName()); } /** * Returns the name of the current realm corresponding to the portal * container that has been stored into the ThreadLocal. If no value can be * found the value of PortalContainer.DEFAULT_REALM_NAME will be used */ public static String getCurrentRealmName() { final String containerName = getCurrentPortalContainerName(); return getRealmName(containerName); } /** * Returns the name of the realm corresponding to the given portal container * name * * @param portalContainerName * the name of the portal container for which we want the name of * the realm */ public static String getRealmName(String portalContainerName) { if (CONFIG == null) { return DEFAULT_REALM_NAME; } return CONFIG.getRealmName(portalContainerName); } /** * Returns the name of the realm corresponding to the current portal * container */ public String getRealmName() { return getRealmName(getName()); } /** * Returns the current value of the setting corresponding to the portal * container that has been stored into the ThreadLocal. If no value can be * found, <code>null</code> will be returned * * @param settingName * the name of the setting wanted */ public static Object getCurrentSetting(String settingName) { final String containerName = getCurrentPortalContainerName(); return getSetting(containerName, settingName); } /** * Returns the value of the setting corresponding to the given portal * container name and the given setting name * * @param portalContainerName * the name of the portal container for which we want the name of * the value of the setting * @param settingName * the name of the setting wanted */ public static Object getSetting(String portalContainerName, String settingName) { if (CONFIG == null) { return null; } return CONFIG.getSetting(portalContainerName, settingName); } /** * Returns the value of the setting corresponding to the current portal * container * * @param settingName * the name of the setting wanted */ public Object getSetting(String settingName) { return getSetting(getName(), settingName); } /** * Indicates if the given servlet context is a dependency of the given * portal container * * @param container * the portal container * @param context * the {@link ServletContext} * @return <code>true</code> if the dependencies matches, <code>false</code> * otherwise; */ public static boolean isScopeValid(ApplicationContainer container, ServletContext context) { if (CONFIG == null) { return true; } return CONFIG.isScopeValid(container.getName(), context.getServletContextName()); } @Managed public boolean isStarted() { return started_; } public void start() { super.start(); started_ = true; } public void stop() { super.stop(); started_ = false; } public static void setInstance(ApplicationContainer instance) { KernelContainerContext.setCurrentContainer(instance); } public static Object getComponent(Class key) { ApplicationContainer pcontainer = getInstanceIfPresent(); return pcontainer.getComponentInstanceOfType(key); } /** * This class is used to compare the {@link WebAppInitContext} */ static class WebAppInitContextComparator implements Comparator<WebAppInitContext> { private final List<String> dependencies; WebAppInitContextComparator(List<String> dependencies) { this.dependencies = dependencies; } /** * This will sort all the {@link WebAppInitContext} such that we will * first have all the web applications defined in the list of * dependencies of the related portal container (see * {@link ApplicationContainerConfig} 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(WebAppInitContext ctx1, WebAppInitContext 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; } } } }