You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
common-smartgears/src/main/java/org/gcube/smartgears/managers/ApplicationManager.java

312 lines
8.9 KiB
Java

package org.gcube.smartgears.managers;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.gcube.common.events.Observes.Kind.critical;
import static org.gcube.smartgears.Constants.context_attribute;
import static org.gcube.smartgears.Constants.profile_file_path;
import static org.gcube.smartgears.lifecycle.application.ApplicationState.active;
import static org.gcube.smartgears.lifecycle.application.ApplicationState.failed;
import static org.gcube.smartgears.lifecycle.application.ApplicationState.stopped;
import static org.gcube.smartgears.provider.ProviderFactory.provider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.List;
import java.util.Map.Entry;
import jakarta.servlet.FilterRegistration;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.ServletRegistration;
import org.gcube.common.events.Observes;
import org.gcube.smartgears.configuration.application.ApplicationHandlers;
import org.gcube.smartgears.context.application.ApplicationContext;
import org.gcube.smartgears.context.container.ContainerContext;
import org.gcube.smartgears.extensions.ApplicationExtension;
import org.gcube.smartgears.extensions.RequestExceptionBarrier;
import org.gcube.smartgears.handlers.application.ApplicationLifecycleEvent;
import org.gcube.smartgears.handlers.application.ApplicationLifecycleHandler;
import org.gcube.smartgears.handlers.application.ApplicationPipeline;
import org.gcube.smartgears.handlers.application.RequestHandler;
import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle;
import org.gcube.smartgears.lifecycle.container.ContainerLifecycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Coordinates management of an application as a gCube resource.
*
* @author Fabio Simeoni
*
*/
public class ApplicationManager {
private static Logger log = LoggerFactory.getLogger(ApplicationManager.class);
private ApplicationPipeline<ApplicationLifecycleHandler> lifecyclePipeline;
private ApplicationContext context;
private List<ApplicationExtension> extensions;
/**
* Starts application management.
*
* @param container
* @param application the context of the application
* @return the context of the application
*/
public ApplicationContext start(ContainerContext container, ServletContext application) {
try {
context = provider().contextFor(container, application);
for (Entry<String,? extends ServletRegistration> servlet : application.getServletRegistrations().entrySet())
log.trace("servlet {} : {} {} ", application.getServletContextName(),servlet.getKey(), servlet.getValue().getMappings());
context.configuration().validate();
saveApplicationState();
// make context available to application in case it is gcube-aware
shareContextWith(application);
// prepare for events as early as possible
registerObservers();
ApplicationHandlers handlers = provider().handlersFor(context);
List<ApplicationLifecycleHandler> lifecycleHandlers = handlers.lifecycleHandlers();
List<RequestHandler> requestHandlers = handlers.requestHandlers();
log.trace("managing {} lifecycle with {}", context.name(), lifecycleHandlers);
log.trace("managing {} requests with {}", context.name(), requestHandlers);
log.trace("extending {} API with {}", context.name(), extensions);
extensions = provider().extensionsFor(context);
// order is important here: first add APIs
registerExtension(extensions);
// then intercept them all
registerHandlersAsFilter(requestHandlers);
// start lifecycle management
start(lifecycleHandlers);
//adding the context name to the configuration
context.configuration().context(application.getContextPath());
// we're in business
context.lifecycle().moveTo(active);
return context;
} catch (RuntimeException e) {
if (context != null) {
log.error("error starting application {}",context.name(), e);
context.lifecycle().moveTo(failed);
} else
log.error("error starting application with context not initialized",e );
throw e;
}
}
private void saveApplicationState() {
File file = context.persistence().file(profile_file_path);
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))){
oos.writeObject(context.id());
}catch (Exception e) {
log.error("error serializing application {} state", context.name());
throw new RuntimeException(e);
}
}
/**
* Stops application management.
*
*/
public void stop() {
if (context == null)
return;
log.info("stopping {} management", context.name());
try {
context.lifecycle().tryMoveTo(stopped);
context.events().fire(context, ApplicationLifecycle.stop);
if (extensions != null)
unregister(extensions);
stopLifecycleHandlers();
log.info("stopping application events for {}", context.name());
context.events().stop();
} catch (Exception e) {
log.warn("cannot stop {} management (see cause)", context.name(), e);
}
}
private void registerHandlersAsFilter(List<RequestHandler> rqHandlers) {
ServletContext app = context.application();
String appName = app.getContextPath().replace("/", "");
RequestManager requestFilter = new RequestManager(context, appName, rqHandlers);
FilterRegistration.Dynamic filter = app.addFilter(appName + "-filter", requestFilter);
filter.addMappingForUrlPatterns(null, false, "/*");
}
private void registerExtension(List<ApplicationExtension> extensions) {
ServletContext application = context.application();
for (ApplicationExtension extension : extensions)
try {
extension.init(context);
if (context.configuration().includes().isEmpty()) {
//register excludes for extension in case of includes they are excluded by default
context.configuration().excludes().addAll(extension.excludes());
}
String mapping = extension.mapping();
application.addServlet(context.configuration().name() + "-" + extension.name(), extension)
.addMapping(mapping);
// adds a filter to map request exceptions onto error responses,
// repeating for our extensions what we already do for our filters.
// we do not interfere with error management of native application servlets
RequestExceptionBarrier barrier = new RequestExceptionBarrier();
FilterRegistration.Dynamic filter = application.addFilter("exception-barrier", barrier);
filter.addMappingForUrlPatterns(null, false, mapping);
log.info("registered API extension {} @ {}", extension.name(), mapping);
} catch (Exception e) {
throw new RuntimeException("cannot register API extension " + extension.name(), e);
}
}
private void unregister(List<ApplicationExtension> extensions) {
for (ApplicationExtension extension : extensions)
extension.stop();
}
private void start(List<ApplicationLifecycleHandler> handlers) {
try {
lifecyclePipeline = new ApplicationPipeline<ApplicationLifecycleHandler>(handlers);
lifecyclePipeline.forward(new ApplicationLifecycleEvent.Start(context));
} catch (RuntimeException e) {
context.lifecycle().tryMoveTo(failed);
throw e;
}
}
private void stopLifecycleHandlers() {
if (lifecyclePipeline == null)
return;
// copy pipeline, flip it, and
ApplicationPipeline<ApplicationLifecycleHandler> returnPipeline = lifecyclePipeline.reverse();
// start lifetime pipeline in inverse order with stop event
returnPipeline.forward(new ApplicationLifecycleEvent.Stop(context));
}
private void registerObservers() {
Object observer = new Object() {
@Observes(value = ContainerLifecycle.stop, kind = critical)
void onStopOf(ContainerLifecycle ignore) {
if (!context.lifecycle().tryMoveTo(stopped))
log.warn("cannot stop {} after container has stopped", context.name());
}
};
context.container().events().subscribe(observer);
// we cleanup when container stops
context.application().addListener(new ServletContextListener() {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("initilizing context {} ",context.name());
context.events().fire(context, ApplicationLifecycle.activation);
log.info("webApp {} initialized ",context.name());
}
//when the container shuts down we go down
@Override
public void contextDestroyed(ServletContextEvent sce) {
try {
log.info("stopping {} on undeployment|shutdown",context.name());
stop();
log.info("suspending undeployment|shutdow to allow {} to stop gracefully",context.name());
SECONDS.sleep(3);
log.info("resuming undeployment|shutdow after stopping {}",context.name());
}
catch (InterruptedException e) {
log.warn(context.name()+" cannot gracefully stop on undeployment|shutdow",e);
}
}
});
}
private void shareContextWith(ServletContext application) {
application.setAttribute(context_attribute, context);
}
}