package org.etk.kernel.container;
import java.io.File;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
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.PriorityBlockingQueue;
import javax.servlet.ServletContext;
import org.etk.common.logging.Logger;
import org.etk.kernel.container.configuration.ConfigurationManager;
import org.etk.kernel.container.configuration.ConfigurationManagerImpl;
import org.etk.kernel.container.configuration.MockConfigurationManagerImpl;
import org.etk.kernel.container.definition.ApplicationContainerConfig;
import org.etk.kernel.container.definition.PortalContainerDefinition;
import org.etk.kernel.container.mock.servlet.MockServletContext;
import org.etk.kernel.container.monitor.jvm.J2EEServerInfo;
import org.etk.kernel.container.monitor.jvm.OperatingSystemInfo;
import org.etk.kernel.container.util.ContainerUtil;
import org.etk.kernel.container.xml.Configuration;
import org.etk.kernel.management.annotations.Managed;
import org.etk.kernel.management.annotations.ManagedDescription;
import org.etk.kernel.management.jmx.annotations.NamingContext;
import org.etk.kernel.management.jmx.annotations.Property;
@Managed
@NamingContext(@Property(key = "container", value = "root"))
public class RootContainer extends KernelContainer {
/**
* Serial Version UID
*/
private static final long serialVersionUID = 812448359436635438L;
/**
* The field is volatile to properly implement the double checked locking
* pattern.
*/
private static volatile RootContainer singleton_;
private OperatingSystemInfo osenv_;
private ApplicationContainerConfig config_;
private static final Logger log = Logger.getLogger(RootContainer.class);
private static volatile boolean booting = false;
private final J2EEServerInfo serverenv_ = new J2EEServerInfo();
private final Set<String> profiles;
/**
* The list of all the tasks to execute while initializing the corresponding
* portal containers
*/
private final 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 final Queue<WebAppInitContext> portalContexts = new ConcurrentLinkedQueue<WebAppInitContext>();
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(KernelContainer.getProfiles());
// Log the active profiles
log.info("Active profiles " + profiles);
//
Runtime.getRuntime().addShutdownHook(new ShutdownThread(this));
this.profiles = profiles;
this.registerComponentInstance(J2EEServerInfo.class, serverenv_);
}
public OperatingSystemInfo getOSEnvironment() {
if (osenv_ == null) {
osenv_ = (OperatingSystemInfo) this.getComponentInstanceOfType(OperatingSystemInfo.class);
}
return osenv_;
}
/**
* @return the {@link ApplicationContainerConfig} corresponding to the
* {@link RootContainer}
*/
ApplicationContainerConfig getPortalContainerConfig() {
if (config_ == null) {
config_ = (ApplicationContainerConfig) this.getComponentInstanceOfType(ApplicationContainerConfig.class);
}
return config_;
}
/**
* Indicates if the current instance is aware of the
* {@link ApplicationContainerConfig}
*
* @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_;
}
public ApplicationContainer getPortalContainer(String name) {
ApplicationContainer pcontainer = (ApplicationContainer) this.getComponentInstance(name);
if (pcontainer == null) {
J2EEServerInfo senv = getServerEnvironment();
if ("standalone".equals(senv.getServerName()) || "test".equals(senv.getServerName())) {
try {
MockServletContext scontext = new MockServletContext(name);
pcontainer = new ApplicationContainer(this, scontext);
ApplicationContainer.setInstance(pcontainer);
ConfigurationManagerImpl cService = new MockConfigurationManagerImpl(scontext);
cService.addConfiguration(ContainerUtil.getConfigurationURL("conf/root-configuration.xml"));
cService.addConfiguration(ContainerUtil.getConfigurationURL("conf/application/application-configuration.xml"));
cService.processRemoveConfiguration();
pcontainer.registerComponentInstance(ConfigurationManager.class, cService);
registerComponentInstance(name, pcontainer);
pcontainer.start(true);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
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) {
ApplicationContainerConfig config = getPortalContainerConfig();
// Ensure that the portal container has been registered
config.registerPortalContainerName(context.getServletContextName());
if (config.hasDefinition()) {
// The new behavior has been detected thus, the creation will be
// done at the end asynchronously
portalContexts.add(new WebAppInitContext(context));
// We assume that a ServletContext of a portal container owns
// configuration files
final PortalContainerPreInitTask task = new PortalContainerPreInitTask() {
public void execute(ServletContext context, ApplicationContainer portalContainer) {
portalContainer.registerContext(context);
}
};
ApplicationContainer.addInitTask(context, task);
} else {
// 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();
WebAppInitContext context;
boolean hasChanged = false;
try {
while ((context = portalContexts.poll()) != null) {
// 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);
}
}
ApplicationContainerConfig config = getPortalContainerConfig();
for (String portalContainerName : initTasks.keySet()) {
// Unregister name of portal container that doesn't exist
log.warn("The portal container '"
+ portalContainerName
+ "' doesn't not exist or"
+ " it has not yet been registered, please check your PortalContainerDefinitions and "
+ "the loading order.");
config.unregisterPortalContainerName(portalContainerName);
}
// remove all the unneeded tasks
initTasks.clear();
}
/**
* Creates the portalContainer base on the ServletContext.
* @param context
*/
public synchronized void createPortalContainer(ServletContext context) {
// Keep the old ClassLoader
final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
boolean hasChanged = false;
final String portalContainerName = context.getServletContextName();
try {
log.info("Trying to create the portal container '" + portalContainerName + "'");
ApplicationContainer pcontainer = new ApplicationContainer(this, context);
ApplicationContainer.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);
// add configs from services
try {
cService.addConfiguration(ContainerUtil.getConfigurationURL("conf/application/application-configuration.xml"));
} catch (Exception ex) {
log.error("Cannot add configuration conf/application/configuration.xml. ServletContext: " + context, ex);
}
// Add configuration that depends on the environment
String uri;
if (serverenv_.isJBoss()) {
uri = "conf/application/jboss-configuration.xml";
} else {
uri = "conf/application/generic-configuration.xml";
}
Collection 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 = singleton_.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 {
ApplicationContainer.setInstance(null);
} catch (Exception e) {
log.warn("An error occured while cleaning the ThreadLocal", e);
}
}
}
synchronized public void removePortalContainer(ServletContext servletContext) {
this.unregisterComponent(servletContext.getServletContextName());
}
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 {
RootContainer rootContainer = new RootContainer();
ConfigurationManagerImpl service = new ConfigurationManagerImpl(rootContainer.profiles);
service.addConfiguration(ContainerUtil.getConfigurationURL("conf/configuration.xml"));
if (System.getProperty("maven.etk.dir") != null) {
service.addConfiguration(ContainerUtil.getConfigurationURL("conf/root-configuration.xml"));
}
String confDir = rootContainer.getServerEnvironment().getExoConfigurationDirectory();
String overrideConf = confDir + "/configuration.xml";
File file = new File(overrideConf);
if (file.exists()) {
service.addConfiguration("file:" + overrideConf);
}
service.processRemoveConfiguration();
rootContainer.registerComponentInstance(ConfigurationManager.class, service);
rootContainer.start(true);
return rootContainer;
} catch (Exception e) {
log.error("Could not build root container", e);
return null;
}
}
/**
* 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) {
throw new IllegalStateException("Already booting by the same thread");
} else {
booting = 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)");
KernelContainerContext.setTopContainer(result);
singleton_ = result;
log.info("Root container booted");
} else {
log.error("Failed to boot root container");
}
} finally {
booting = false;
}
}
}
}
}
return result;
}
static public void setInstance(RootContainer rcontainer) {
singleton_ = rcontainer;
}
@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();
}
/**
* 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, context.getServletContextName());
}
/**
* 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 appContainer
* the name of the portal container on which the task must be
* executed
*/
public void addInitTask(ServletContext context, PortalContainerInitTask task, String appContainer) {
final ApplicationContainer container = getPortalContainer(appContainer);
if (!task.alreadyExists(container)) {
if (log.isDebugEnabled())
log.debug("The application container '" + appContainer + "' has not yet been initialized, thus the task can be added");
ConcurrentMap<String, Queue<PortalContainerInitTaskContext>> queues = initTasks.get(appContainer);
if (queues == null) {
queues = new ConcurrentHashMap<String, Queue<PortalContainerInitTaskContext>>();
final ConcurrentMap<String, Queue<PortalContainerInitTaskContext>> q = initTasks.putIfAbsent(appContainer, 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(appContainer);
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;
}
}
queue.add(new PortalContainerInitTaskContext(context, task));
} else {
if (log.isDebugEnabled())
log.debug("The portal container '" + appContainer + "' has already been initialized, thus we call onAlreadyExists");
ApplicationContainer oldPortalContainer = ApplicationContainer.getInstanceIfPresent();
try {
ApplicationContainer.setInstance(container);
task.onAlreadyExists(context, container);
} finally {
ApplicationContainer.setInstance(oldPortalContainer);
}
}
}
/**
* 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(ApplicationContainer 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;
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);
}
}
queues.remove(type);
if (queues.isEmpty()) {
initTasks.remove(portalContainerName);
}
if (log.isDebugEnabled())
log.debug("End launching the " + type + " tasks of the portal container '" + portalContainer + "'");
}
static class ShutdownThread extends Thread {
RootContainer container_;
ShutdownThread(RootContainer container) {
container_ = container;
}
public void run() {
container_.stop();
}
}
public void stop() {
super.stop();
KernelContainerContext.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(ApplicationContainer 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, ApplicationContainer 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, ApplicationContainer 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(ApplicationContainer portalContainer) {
return portalContainer != null && portalContainer.isStarted();
}
/**
* {@inheritDoc}
*/
public final void onAlreadyExists(ServletContext context, ApplicationContainer 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(ApplicationContainer portalContainer) {
return portalContainer != null;
}
/**
* {@inheritDoc}
*/
public final void onAlreadyExists(ServletContext context, ApplicationContainer portalContainer) {
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 '"
+ context.getServletContextName() + "'");
}
/**
* {@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(ApplicationContainer portalContainer) {
return portalContainer != null && portalContainer.isStarted();
}
/**
* {@inheritDoc}
*/
public final void onAlreadyExists(ServletContext context, ApplicationContainer 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 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(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;
}
}
}
}