diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationShutdownEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationShutdownEvent.java new file mode 100644 index 000000000..ba7f50bac --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationShutdownEvent.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.net.event; + +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.event.listener.Listener; + +/** + * Event that is dispatched when the web application has nearly completely shut + * down, including the authentication/authorization portion of extensions. Any + * installed extensions are still loaded (such that they may receive this event + * via {@link Listener#handleEvent(java.lang.Object)}, but their authentication + * providers will have been shut down via {@link AuthenticationProvider#shutdown()}, + * and resources from user sessions will have been closed and released via + * {@link UserContext#invalidate()}. + */ +public interface ApplicationShutdownEvent { +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationStartedEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationStartedEvent.java new file mode 100644 index 000000000..cf2ae2f3b --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/ApplicationStartedEvent.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.net.event; + +/** + * Event that is dispatched when the web application has finished starting up, + * including all extensions. This event indicates only that the web application + * startup process has completed and all extensions have been loaded. It does + * not indicate whether all extensions have started successfully. + */ +public interface ApplicationStartedEvent { +} diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java index 2b3f155d0..1068477c4 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleServletContextListener.java @@ -35,11 +35,14 @@ import org.apache.guacamole.environment.LocalEnvironment; import org.apache.guacamole.extension.ExtensionModule; import org.apache.guacamole.log.LogModule; import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.event.ApplicationShutdownEvent; +import org.apache.guacamole.net.event.ApplicationStartedEvent; import org.apache.guacamole.properties.BooleanGuacamoleProperty; import org.apache.guacamole.properties.FileGuacamoleProperties; import org.apache.guacamole.rest.RESTServiceModule; import org.apache.guacamole.rest.auth.HashTokenSessionMap; import org.apache.guacamole.rest.auth.TokenSessionMap; +import org.apache.guacamole.rest.event.ListenerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -125,7 +128,13 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener */ @Inject private List temporaryFiles; - + + /** + * Service for dispatching events to registered listeners. + */ + @Inject + private ListenerService listenerService; + /** * Internal reference to the Guice injector that was lazily created when * getInjector() was first invoked. @@ -179,6 +188,17 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener // Store reference to injector for use by Jersey and HK2 bridge servletContextEvent.getServletContext().setAttribute(GUICE_INJECTOR, injector); + // Inform any listeners that application startup has completed + try { + listenerService.handleEvent(new ApplicationStartedEvent() { + // The application startup event currently has no content + }); + } + catch (GuacamoleException e) { + logger.error("An extension listening for application startup failed: {}", e.getMessage()); + logger.debug("Extension failed internally while handling the application startup event.", e); + } + } @Override @@ -228,19 +248,36 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener // Clean up reference to Guice injector servletContextEvent.getServletContext().removeAttribute(GUICE_INJECTOR); - // Shutdown TokenSessionMap + // Shutdown TokenSessionMap, invalidating all sessions (logging all + // users out) if (sessionMap != null) sessionMap.shutdown(); - // Unload all extensions + // Unload authentication for all extensions if (authProviders != null) { for (AuthenticationProvider authProvider : authProviders) authProvider.shutdown(); } + // Inform any listeners that application shutdown has completed + try { + listenerService.handleEvent(new ApplicationShutdownEvent() { + // The application shutdown event currently has no content + }); + } + catch (GuacamoleException e) { + logger.error("An extension listening for application shutdown failed: {}", e.getMessage()); + logger.debug("Extension failed internally while handling the application shutdown event.", e); + } + } finally { + // NOTE: This temporary file cleanup must happen AFTER firing the + // ApplicationShutdownEvent, or an extension that relies on a .jar + // file among those temporary files might fail internally when + // attempting to process the event. + // Regardless of what may succeed/fail here, always attempt to // clean up ALL temporary files if (temporaryFiles != null) diff --git a/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java b/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java index def5c19d9..f3b804892 100644 --- a/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java @@ -22,6 +22,8 @@ package org.apache.guacamole.event; import javax.annotation.Nonnull; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleResourceNotFoundException; +import org.apache.guacamole.net.event.ApplicationShutdownEvent; +import org.apache.guacamole.net.event.ApplicationStartedEvent; import org.apache.guacamole.net.event.DirectoryEvent; import org.apache.guacamole.net.event.DirectoryFailureEvent; import org.apache.guacamole.net.event.DirectorySuccessEvent; @@ -115,12 +117,19 @@ public class EventLoggingListener implements Listener { @Override public void handleEvent(@Nonnull Object event) throws GuacamoleException { + // General object creation/modification/deletion if (event instanceof DirectorySuccessEvent) logSuccess((DirectorySuccessEvent) event); - else if (event instanceof DirectoryFailureEvent) logFailure((DirectoryFailureEvent) event); + // Application startup/shutdown + else if (event instanceof ApplicationStartedEvent) + logger.info("The Apache Guacamole web application has started."); + else if (event instanceof ApplicationShutdownEvent) + logger.info("The Apache Guacamole web application has shut down."); + + // Unknown events else logger.debug("Ignoring unknown/unimplemented event type: {}", event.getClass());