2016-11-25 15:42:29 +01:00
|
|
|
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.Collection;
|
|
|
|
import java.util.List;
|
2017-05-12 17:18:07 +02:00
|
|
|
import java.util.Map.Entry;
|
2016-11-25 15:42:29 +01:00
|
|
|
|
|
|
|
import javax.servlet.FilterRegistration;
|
|
|
|
import javax.servlet.ServletContext;
|
|
|
|
import javax.servlet.ServletContextEvent;
|
|
|
|
import javax.servlet.ServletContextListener;
|
|
|
|
import javax.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;
|
|
|
|
|
2023-02-06 17:34:18 +01:00
|
|
|
private List<ApplicationExtension> extensions;
|
2023-02-01 14:40:31 +01:00
|
|
|
|
2016-11-25 15:42:29 +01:00
|
|
|
/**
|
|
|
|
* 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);
|
2020-01-23 15:24:19 +01:00
|
|
|
|
2017-05-12 17:18:07 +02:00
|
|
|
for (Entry<String,? extends ServletRegistration> servlet : application.getServletRegistrations().entrySet())
|
|
|
|
log.trace("servlet {} : {} {} ", application.getServletContextName(),servlet.getKey(), servlet.getValue().getMappings());
|
2022-02-07 09:44:31 +01:00
|
|
|
|
2022-06-10 17:08:44 +02:00
|
|
|
context.configuration().validate();
|
|
|
|
|
2016-11-25 15:42:29 +01:00
|
|
|
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);
|
|
|
|
|
2023-02-06 17:34:18 +01:00
|
|
|
/*
|
2023-02-01 14:40:31 +01:00
|
|
|
extensions = provider().extensionsFor(context);
|
2016-11-25 15:42:29 +01:00
|
|
|
extensions.validate();
|
2023-02-06 17:34:18 +01:00
|
|
|
*/
|
|
|
|
|
2016-11-25 15:42:29 +01:00
|
|
|
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);
|
|
|
|
|
2023-02-06 17:34:18 +01:00
|
|
|
extensions = provider().extensionsFor(context);
|
2016-11-25 15:42:29 +01:00
|
|
|
// order is important here: first add APIs
|
2023-02-06 17:34:18 +01:00
|
|
|
registerExtension(extensions);
|
2016-11-25 15:42:29 +01:00
|
|
|
|
|
|
|
// then intercept them all
|
2023-02-06 17:34:18 +01:00
|
|
|
registerHandlers(requestHandlers);
|
2016-11-25 15:42:29 +01:00
|
|
|
|
|
|
|
// 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) {
|
2020-01-23 15:24:19 +01:00
|
|
|
|
|
|
|
|
2016-11-25 15:42:29 +01:00
|
|
|
|
2018-12-10 18:36:19 +01:00
|
|
|
if (context != null) {
|
|
|
|
log.error("error starting application {}",context.name(), e);
|
2016-11-25 15:42:29 +01:00
|
|
|
context.lifecycle().moveTo(failed);
|
2018-12-10 18:36:19 +01:00
|
|
|
} else
|
|
|
|
log.error("error starting application with context not initialized",e );
|
2016-11-25 15:42:29 +01:00
|
|
|
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private void saveApplicationState() {
|
2023-02-06 17:34:18 +01:00
|
|
|
File file = context.persistence().file(profile_file_path);
|
2016-11-25 15:42:29 +01:00
|
|
|
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);
|
2023-02-01 14:40:31 +01:00
|
|
|
|
|
|
|
if (extensions != null)
|
|
|
|
unregister(extensions);
|
|
|
|
|
2016-11-25 15:42:29 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-02-06 17:34:18 +01:00
|
|
|
private void registerHandlers(List<RequestHandler> rqHandlers) {
|
2016-11-25 15:42:29 +01:00
|
|
|
|
|
|
|
ServletContext app = context.application();
|
|
|
|
|
|
|
|
// attach filters based on request pipeline to each servlet
|
|
|
|
Collection<? extends ServletRegistration> servlets = app.getServletRegistrations().values();
|
|
|
|
|
|
|
|
for (ServletRegistration servlet : servlets) {
|
|
|
|
|
|
|
|
String name = servlet.getName();
|
|
|
|
|
|
|
|
if (name.equals("default") || name.equals("jsp")) // skip page-resolving servlets
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (String mapping : servlet.getMappings()) {
|
|
|
|
|
|
|
|
RequestManager requestFilter = new RequestManager(context, name, rqHandlers);
|
|
|
|
|
|
|
|
FilterRegistration.Dynamic filter = app.addFilter(name + "-filter-"+mapping.replaceAll("/", ""), requestFilter);
|
|
|
|
|
|
|
|
filter.addMappingForUrlPatterns(null, false, mapping);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-06 17:34:18 +01:00
|
|
|
private void registerExtension(List<ApplicationExtension> extensions) {
|
2016-11-25 15:42:29 +01:00
|
|
|
|
|
|
|
ServletContext application = context.application();
|
|
|
|
|
2023-02-06 17:34:18 +01:00
|
|
|
for (ApplicationExtension extension : extensions)
|
2016-11-25 15:42:29 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
extension.init(context);
|
|
|
|
|
2020-01-23 15:24:19 +01:00
|
|
|
|
2018-12-10 18:36:19 +01:00
|
|
|
if (context.configuration().includes().isEmpty()) {
|
|
|
|
//register excludes for extension in case of includes they are excluded by default
|
|
|
|
context.configuration().excludes().addAll(extension.excludes());
|
|
|
|
}
|
2020-01-23 15:24:19 +01:00
|
|
|
|
2016-11-25 15:42:29 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2023-02-01 14:40:31 +01:00
|
|
|
|
2023-02-06 17:34:18 +01:00
|
|
|
private void unregister(List<ApplicationExtension> extensions) {
|
2023-02-01 14:40:31 +01:00
|
|
|
|
2023-02-06 17:34:18 +01:00
|
|
|
for (ApplicationExtension extension : extensions)
|
2023-02-01 14:40:31 +01:00
|
|
|
extension.stop();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-11-25 15:42:29 +01:00
|
|
|
|
|
|
|
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());
|
2022-06-10 17:08:44 +02:00
|
|
|
|
2022-06-22 18:50:54 +02:00
|
|
|
context.events().fire(context, ApplicationLifecycle.activation);
|
2016-11-25 15:42:29 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|