package org.alien4cloud.bootstrap;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import alien4cloud.FullApplicationConfiguration;
import alien4cloud.audit.rest.AuditController;
import alien4cloud.common.AlienConstants;
import alien4cloud.events.AlienEvent;
import alien4cloud.events.HALeaderElectionEvent;
import alien4cloud.webconfiguration.RestDocumentationHandlerProvider;
import alien4cloud.webconfiguration.RestDocumentationPluginsBootstrapper;
import lombok.extern.slf4j.Slf4j;
/**
* Manage the child context defined in {@link FullApplicationConfiguration}: this context is launched when the current alien instance is elected as leader and
* eventually destroyed in case of banish.
*/
@Component
@Slf4j
public class ApplicationManager implements ApplicationListener<AlienEvent>, HandlerMapping, PriorityOrdered {
@Resource
private ApplicationContext bootstrapContext;
private AnnotationConfigApplicationContext fullApplicationContext;
private RequestMappingHandlerMapping mapper;
private volatile boolean childContextLaunched;
@Value("#{environment.acceptsProfiles('" + AlienConstants.NO_API_DOC_PROFILE + "')}")
private boolean apiDocDisabled;
/**
* A synchronized method is enough since the boolean <code>childContextLaunched</code> is only read/write in it.
*/
@Override
public synchronized void onApplicationEvent(AlienEvent event) {
if (event instanceof HALeaderElectionEvent) {
handleHALeaderElectionEvent((HALeaderElectionEvent) event);
} else {
handleAlienEvent(event);
}
}
/**
* receive an event, and forward it to child contexts.
* Mark it as forwarded before, so that if wont be re-processed here, as an event published into a context is automatically published into the parent
* context.
*
* @param event
*/
private void handleAlienEvent(AlienEvent event) {
if (childContextLaunched) {
if (!event.isForwarded()) {
event.setForwarded(true);
fullApplicationContext.publishEvent(event);
} else {
log.debug("Event {} already forwarded to children", event);
}
} else {
log.debug("This instance is a backup. It doesn't have to process events.");
}
}
/**
* configure this host as leader / backup
*
* @param event
*/
private void handleHALeaderElectionEvent(HALeaderElectionEvent event) {
if (event.isLeader()) {
log.info("Leader Election event received, this instance is now known as the leader");
if (!childContextLaunched) {
log.info("Launching the full application context");
fullApplicationContext = new AnnotationConfigApplicationContext();
fullApplicationContext.setParent(this.bootstrapContext);
fullApplicationContext.setId(this.bootstrapContext.getId() + ":full");
fullApplicationContext.register(FullApplicationConfiguration.class);
fullApplicationContext.refresh();
fullApplicationContext.start();
mapper = new RequestMappingHandlerMapping();
mapper.setApplicationContext(fullApplicationContext);
mapper.afterPropertiesSet();
refreshDocumentation();
AuditController auditController = fullApplicationContext.getBean(AuditController.class);
auditController.register(mapper);
childContextLaunched = true;
} else {
log.warn("The full application context is already launched, something seems wrong in the current state !");
}
} else {
if (childContextLaunched) {
log.info("Destroying the full application context");
mapper = null;
fullApplicationContext.destroy();
childContextLaunched = false;
} else {
log.warn("The full application context is already destroyed, something seems wrong in the current state !");
}
}
}
private void refreshDocumentation() {
if (!apiDocDisabled) {
RestDocumentationHandlerProvider restDocumentationHandlerProvider = fullApplicationContext.getBean(RestDocumentationHandlerProvider.class);
restDocumentationHandlerProvider.register(mapper);
RestDocumentationPluginsBootstrapper documentationPluginsBootstrapper = fullApplicationContext.getBean(RestDocumentationPluginsBootstrapper.class);
documentationPluginsBootstrapper.refresh();
}
}
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (mapper != null) {
return mapper.getHandler(request);
}
return null;
}
@Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}