Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
no.mnemonic.commons.container.ComponentContainer Maven / Gradle / Ivy
package no.mnemonic.commons.container;
import no.mnemonic.commons.component.*;
import no.mnemonic.commons.component.ComponentState;
import no.mnemonic.commons.container.plugins.ComponentContainerPlugin;
import no.mnemonic.commons.container.plugins.ComponentDependencyResolver;
import no.mnemonic.commons.container.plugins.ComponentLifecycleHandler;
import no.mnemonic.commons.container.plugins.ComponentValidator;
import no.mnemonic.commons.container.plugins.impl.*;
import no.mnemonic.commons.container.providers.BeanProvider;
import no.mnemonic.commons.container.providers.SimpleBeanProvider;
import no.mnemonic.commons.logging.Logger;
import no.mnemonic.commons.logging.Logging;
import no.mnemonic.commons.utilities.ObjectUtils;
import no.mnemonic.commons.utilities.collections.MapUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import static no.mnemonic.commons.component.ComponentState.*;
import static no.mnemonic.commons.utilities.collections.ListUtils.list;
import static no.mnemonic.commons.utilities.lambda.LambdaUtils.*;
/**
* ComponentContainer
*/
public class ComponentContainer implements Component, ComponentListener, ComponentListenerAspect, ComponentStatusAspect, ComponentStateAspect {
//nodes and state
private final BeanProvider beans;
private final Set initializedComponents = Collections.synchronizedSet(new HashSet<>());
private final Map nodes = new ConcurrentHashMap<>();
private final Map objectNodeMap = new ConcurrentHashMap<>();
//parent and child containers
private final ComponentContainer parent;
private final AtomicReference state = new AtomicReference<>(NOT_STARTED);
private final Collection childContainers = Collections.synchronizedCollection(new ArrayList<>());
//timestamps and metrics
private final AtomicLong lastStoppingNotificationTimestamp = new AtomicLong();
//listeners
private final Collection componentListeners = new HashSet<>();
private final Collection containerListeners = new HashSet<>();
private final Collection dependencyResolvers = new HashSet<>();
private final Collection lifecycleManagers = new HashSet<>();
private final Collection validators = new HashSet<>();
private final Object STATE_LOCK = new Object();
private final Logger LOGGER = Logging.getLogger(ComponentContainer.class.getName());
//creators
/**
* @param beans beans which are administered by this container
* @param parent component container
*/
private ComponentContainer(BeanProvider beans, ComponentContainer parent) {
this.beans = beans;
this.parent = parent;
if (parent != null) {
synchronized (parent.STATE_LOCK) {
parent.childContainers.add(this);
}
this.addComponentListener(parent);
}
}
public static ComponentContainer create(Object... beans) {
return new ComponentContainer(new SimpleBeanProvider(list(beans)), null);
}
public static ComponentContainer create(BeanProvider provider) {
return new ComponentContainer(provider, null);
}
// interface methods
@Override
public void addComponentListener(ComponentListener listener) {
componentListeners.add(listener);
}
@Override
public Collection getComponentListeners() {
return Collections.unmodifiableCollection(componentListeners);
}
@Override
public void removeComponentListener(ComponentListener listener) {
componentListeners.remove(listener);
}
public void addContainerListener(ContainerListener listener) {
containerListeners.add(listener);
}
public void removeContainerListener(ContainerListener listener) {
containerListeners.remove(listener);
}
@Override
public ComponentStatus getComponentStatus() {
return new ComponentStatus();
}
@Override
public ComponentState getComponentState() {
return state.get();
}
@Override
public void notifyComponentStopping(Component component) {
if (getComponentState().isTerminal()) {
if (getLogger().isDebug())
getLogger().debug("Component " + component + " notified us of current shutdown");
}
}
@Override
public void notifyComponentStopped(Component component) {
//if notifying component is part of this container, destroy this container now (unless already terminating)
if (initializedComponents.contains(component) && !getComponentState().isTerminal()) {
getLogger().warning("Component " + component + " stopped, destroying container " + this);
try {
this.destroy();
} catch (Exception e) {
getLogger().error("Error when calling destroy", e);
}
}
}
//public methods
/**
* Initialize this container, and any subcontainers it may have
*/
public ComponentContainer initialize() {
try {
if (getComponentState() != NOT_STARTED) return this;
// update state
setState(INITIALIZING);
// initialize parent container if not already so
if (parent != null) {
// make sure parent is initialized first
parent.initialize();
} else {
// create a thread that will shutdown the container
new ShutdownTask(this);
}
// handle container plugins
handleContainerPlugins();
// create component nodes
createNodes();
// resolve dependencies between components
resolveDependencies();
// validate configuration
validate();
// activate components
activate();
// update state
setState(STARTED);
// notify listeners
containerListeners.forEach(l -> tryTo(() -> l.notifyContainerStarted(this), e -> LOGGER.error(e, "Error calling notifyContainerStarted")));
} catch (RuntimeException e) {
getLogger().error("Error initializing container", e);
//if startup fails, make sure to exit the container
destroy();
throw e;
}
return this;
}
/**
* Destroy only the current container (along with any child containers which cannot survive without their parent)
* Parent containers are untouched
*/
public ComponentContainer destroy() {
try {
// only allow one thread to attempt shutdown for any container
synchronized (STATE_LOCK) {
if (getComponentState().isTerminal()) return this;
setState(STOPPING);
STATE_LOCK.notifyAll();
}
getLogger().warning("Shutting down...");
fireContainerStopping();
// shut down child containers first
getChildContainers().forEach(ComponentContainer::destroy);
// stop all nodes in this container
nodes.values().forEach(this::stopNode);
// stop nodes not registered in initial dependency graph
initializedComponents.clear();
} catch (RuntimeException e) {
getLogger().error("Error in destroy()", e);
} finally {
// remove parent reference
if (parent != null) {
synchronized (parent.STATE_LOCK) {
parent.childContainers.remove(this);
}
}
getLogger().warning("Shutdown complete");
setState(STOPPED);
nodes.clear();
//notify componentListeners that we are done
fireContainerStopped();
}
return this;
}
// ***************************** private methods
private Logger getLogger() {
return LOGGER;
}
Map getComponents() {
synchronized (STATE_LOCK) {
return MapUtils.map(nodes.entrySet(), e -> MapUtils.Pair.T(e.getKey(), e.getValue().getObject()));
}
}
private Collection getChildContainers() {
synchronized (STATE_LOCK) {
return Collections.unmodifiableCollection(list(childContainers));
}
}
private void fireContainerStopping() {
//avoid excessive notifications
if (System.currentTimeMillis() - lastStoppingNotificationTimestamp.get() < 1000) return;
if (getComponentState().isTerminal()) {
lastStoppingNotificationTimestamp.set(System.currentTimeMillis());
componentListeners.forEach(l -> tryTo(() -> l.notifyComponentStopping(ComponentContainer.this), e -> LOGGER.error(e, "Error calling notifyComponentStopped")));
containerListeners.forEach(l -> tryTo(() -> l.notifyContainerDestroying(ComponentContainer.this), e -> LOGGER.error(e, "Error calling notifyContainerDestroying")));
}
}
private void fireContainerStopped() {
componentListeners.forEach(l -> tryTo(() -> l.notifyComponentStopped(this), e -> LOGGER.error(e, "Error calling notifyComponentStopped")));
containerListeners.forEach(l -> tryTo(() -> l.notifyContainerDestroyed(this), e -> LOGGER.error(e, "Error calling notifyContainerDestroyed")));
}
private void setState(ComponentState state) {
synchronized (STATE_LOCK) {
this.state.set(state);
STATE_LOCK.notifyAll();
}
}
/**
* Set all special purpose objects to special interfaces
*/
private void handleContainerPlugins() {
//make all container aware beans aware of its parent container
beans.getBeans(ContainerAware.class).forEach((k, v) -> v.registerContainerAware(this));
//register all plugins
beans.getBeans(ComponentContainerPlugin.class).forEach((k, v) -> registerPlugin(v));
//add handler plugin to register all container listeners
registerPlugin(new ContainerListenerHandler(this));
//add handler plugin to register all component listeners
registerPlugin(new ComponentListenerAspectHandler(this));
lifecycleManagers.addAll(beans.getBeans(ComponentLifecycleHandler.class).values());
lifecycleManagers.add(new ComponentLifecycleAspectHandler());
dependencyResolvers.addAll(beans.getBeans(ComponentDependencyResolver.class).values());
dependencyResolvers.add(new MethodAnnotationDependencyResolver());
dependencyResolvers.add(new FieldAnnotationDependencyResolver());
validators.addAll(beans.getBeans(ComponentValidator.class).values());
validators.add(new ComponentValidationAspectValidator());
}
/**
* Validate all components that has a validator
*/
private void validate() {
ValidationContext validationContext = new ValidationContext();
beans.getBeans().values().forEach(b -> validateBean(b, validationContext));
for (String error : validationContext.getErrors()) {
getLogger().error(error);
}
for (String warning : validationContext.getWarnings()) {
getLogger().warning(warning);
}
if (!validationContext.isValid()) {
throw new ComponentConfigurationException(validationContext);
}
}
private void registerPlugin(ComponentContainerPlugin plugin) {
Map targets = new HashMap<>();
beans.getBeans().forEach((k, v) -> {
if (plugin.appliesTo(v)) targets.put(k, v);
});
plugin.registerBeans(targets);
}
private void validateBean(Object bean, ValidationContext validationContext) {
for (ComponentValidator v : validators) {
if (v.appliesTo(bean)) {
v.validate(validationContext, bean);
return;
}
}
}
/**
* Activate active components
*/
private void activate() {
try {
getLogger().info("Initializing " + this);
nodes.values().forEach(this::startNode);
if (getLogger().isInfo()) getLogger().info("Initialization complete");
} catch (Exception e) {
getLogger().error("Caught exception during initialization", e);
destroy();
throw new ComponentException(e);
}
}
/**
* Start this node component. Resolves dependencies, so any dependent objects
* are started first, and any objects listed to be started afterwords is
* started afterwords.
*
* @param n node to start
*/
private void startNode(ComponentNode n) {
if (n.isStarted()) return;
n.setStarted(true);
// first start all components which we have an initialization dependency to
n.getInitializationDependencies().forEach(this::startNode);
// see if any lifecycle manager can start this component
for (ComponentLifecycleHandler manager : lifecycleManagers) {
if (!manager.appliesTo(n.getObject())) continue;
getLogger().info("Starting " + n.getObjectName() + "/" + n.getObject());
manager.startComponent(n.getObject());
// mark component as initialized
initializedComponents.add(n.getObject());
}
}
/**
* Stop the given node component. Resolves dependencies, to stop depending
* components first, and successive components afterwords.
*
* @param n node to stop
*/
private void stopNode(ComponentNode n) {
if (!n.isStarted()) return;
n.setStarted(false);
// first stop all components which we have a destruction dependency to
n.getDestructionDependencies().forEach(this::stopNode);
// see if any lifecycle manager can stop this component
for (ComponentLifecycleHandler manager : lifecycleManagers) {
if (!manager.appliesTo(n.getObject())) continue;
try {
getLogger().info("Destroying " + n.getObjectName() + "/" + n.getObject());
manager.stopComponent(n.getObject());
if (getLogger().isDebug()) getLogger().debug("Finished stopComponent for component " + n.getObjectName());
} catch (Exception e) {
getLogger().error("Error calling stopComponent on " + n.getObject(), e);
}
// remove initialization mark
initializedComponents.remove(n.getObject());
}
}
/**
* Build dependency tree
*/
@SuppressWarnings("unchecked")
private void createNodes() {
nodes.clear();
objectNodeMap.clear();
// make all nodes available
beans.getBeans().forEach((oid, o) -> {
ComponentNode n = new ComponentNode(oid, o);
nodes.put(oid, n);
objectNodeMap.put(o, n);
});
}
private void resolveDependencies() {
nodes.keySet().forEach(oid -> resolveDependsOn(nodes.get(oid)));
}
private void resolveDependsOn(ComponentNode node) {
//check for Dependency annotations on getters
getDependencies(node).stream()
.filter(dep -> dep != null)
.forEach(dep -> {
if (dep instanceof Collection) {
//add dependency to each member of collection
((Collection>) dep).stream()
.map(objectNodeMap::get)
.filter(o -> o != null)
.forEach(depnode -> addDependency(node, depnode));
} else {
//add dependency to object
ObjectUtils.ifNotNullDo(objectNodeMap.get(dep), depnode -> addDependency(node, depnode));
}
});
}
private void addDependency(ComponentNode node, ComponentNode dependencyNode) {
node.addInitializationDependency(dependencyNode);
dependencyNode.addDestructionDependency(node);
}
private Collection> getDependencies(ComponentNode node) {
Collection dependencies = new HashSet<>();
dependencyResolvers.forEach(r -> dependencies.addAll(
ObjectUtils.ifNull(r.resolveDependencies(node.getObject()), new HashSet<>())
));
return dependencies;
}
private class ShutdownTask implements Runnable {
private ComponentContainer rootContainer;
ShutdownTask(ComponentContainer rootContainer) {
this.rootContainer = rootContainer;
Runtime.getRuntime().addShutdownHook(new Thread(this));
if (getLogger().isInfo()) rootContainer.getLogger().info("Shutdownhook added");
}
public void run() {
try {
// usedShutdownThread = true;
rootContainer.getLogger().warning("Shutdownhook triggered");
// drop out of this shutdownhook if container is already shut down
synchronized (STATE_LOCK) {
if (getComponentState().isTerminal()) {
rootContainer.getLogger().warning("Shutdownhook aborted, container already shut down");
return;
}
}
rootContainer.destroy();
} finally {
rootContainer.getLogger().warning("Shutdownhook done");
}
}
}
}