GUACAMOLE-364: add extension module support for event listeners

This commit is contained in:
Carl Harris
2017-08-16 06:55:28 -04:00
parent dca7862351
commit 109d57ecb3
3 changed files with 179 additions and 0 deletions

View File

@@ -35,6 +35,8 @@ import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipException; import java.util.zip.ZipException;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import org.apache.guacamole.net.event.listener.Listener;
import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.ObjectMapper;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
@@ -109,6 +111,11 @@ public class Extension {
*/ */
private final Collection<Class<AuthenticationProvider>> authenticationProviderClasses; private final Collection<Class<AuthenticationProvider>> authenticationProviderClasses;
/**
* The collection of all Listener classes defined within the extension.
*/
private final Collection<Class<Listener>> listenerClasses;
/** /**
* The resource for the small favicon for the extension. If provided, this * The resource for the small favicon for the extension. If provided, this
* will replace the default Guacamole icon. * will replace the default Guacamole icon.
@@ -265,6 +272,80 @@ public class Extension {
} }
/**
* Retrieve the Listener subclass having the given name. If
* the class having the given name does not exist or isn't actually a
* subclass of Listener, an exception will be thrown.
*
* @param name
* The name of the Listener class to retrieve.
*
* @return
* The subclass of Listener having the given name.
*
* @throws GuacamoleException
* If no such class exists, or if the class with the given name is not
* a subclass of Listener.
*/
@SuppressWarnings("unchecked") // We check this ourselves with isAssignableFrom()
private Class<Listener> getListenerClass(String name)
throws GuacamoleException {
try {
// Get listener class
Class<?> listenerClass = classLoader.loadClass(name);
// Verify the located class is actually a subclass of Listener
if (!Listener.class.isAssignableFrom(listenerClass))
throw new GuacamoleServerException("Listeners MUST implement a Listener subclass.");
// Return located class
return (Class<Listener>) listenerClass;
}
catch (ClassNotFoundException e) {
throw new GuacamoleException("Listener class not found.", e);
}
catch (LinkageError e) {
throw new GuacamoleException("Listener class cannot be loaded (wrong version of API?).", e);
}
}
/**
* Returns a new collection of all Listener subclasses having the given names.
* If any class does not exist or isn't actually subclass of Listener, an
* exception will be thrown, an no further Listener classes will be loaded.
*
* @param names
* The names of the AuthenticationProvider classes to retrieve.
*
* @return
* A new collection of all AuthenticationProvider subclasses having the
* given names.
*
* @throws GuacamoleException
* If any given class does not exist, or if any given class is not a
* subclass of AuthenticationProvider.
*/
private Collection<Class<Listener>> getListenerClasses(Collection<String> names)
throws GuacamoleException {
// If no classnames are provided, just return an empty list
if (names == null)
return Collections.<Class<Listener>>emptyList();
// Define all auth provider classes
Collection<Class<Listener>> classes = new ArrayList<Class<Listener>>(names.size());
for (String name : names)
classes.add(getListenerClass(name));
// Callers should not rely on modifying the result
return Collections.unmodifiableCollection(classes);
}
/** /**
* Loads the given file as an extension, which must be a .jar containing * Loads the given file as an extension, which must be a .jar containing
* a guac-manifest.json file describing its contents. * a guac-manifest.json file describing its contents.
@@ -363,6 +444,9 @@ public class Extension {
// Define authentication providers // Define authentication providers
authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders()); authenticationProviderClasses = getAuthenticationProviderClasses(manifest.getAuthProviders());
// Define listeners
listenerClasses = getListenerClasses(manifest.getListeners());
// Get small icon resource if provided // Get small icon resource if provided
if (manifest.getSmallIcon() != null) if (manifest.getSmallIcon() != null)
smallIcon = new ClassPathResource(classLoader, "image/png", manifest.getSmallIcon()); smallIcon = new ClassPathResource(classLoader, "image/png", manifest.getSmallIcon());
@@ -488,6 +572,17 @@ public class Extension {
return authenticationProviderClasses; return authenticationProviderClasses;
} }
/**
* Returns all declared listener classes associated wit this extension. Listeners are
* declared within the extension manifest.
*
* @return
* All declared listener classes with this extension.
*/
public Collection<Class<Listener>> getListenerClasses() {
return listenerClasses;
}
/** /**
* Returns the resource for the small favicon for the extension. If * Returns the resource for the small favicon for the extension. If
* provided, this will replace the default Guacamole icon. * provided, this will replace the default Guacamole icon.

View File

@@ -87,6 +87,11 @@ public class ExtensionManifest {
*/ */
private Collection<String> authProviders; private Collection<String> authProviders;
/**
* The names of all listener classes within this extension, if any.
*/
private Collection<String> listeners;
/** /**
* The path to the small favicon. If provided, this will replace the default * The path to the small favicon. If provided, this will replace the default
* Guacamole icon. * Guacamole icon.
@@ -355,6 +360,32 @@ public class ExtensionManifest {
this.authProviders = authProviders; this.authProviders = authProviders;
} }
/**
* Returns the classnames of all listener classes within the extension.
* These classnames are defined within the manifest by the "listeners"
* property as an array of strings, where each string is a listener
* class name.
*
* @return
* a collection of classnames for all listeners within the extension
*/
public Collection<String> getListeners() {
return listeners;
}
/**
* Sets the classnames of all listener classes within the extension.
* These classnames are defined within the manifest by the "listeners"
* property as an array of strings, where each string is a listener
* class name.
*
* @param listeners
* a collection of classnames for all listeners within the extension
*/
public void setListeners(Collection<String> listeners) {
this.listeners = listeners;
}
/** /**
* Returns the path to the small favicon, relative to the root of the * Returns the path to the small favicon, relative to the root of the
* extension. * extension.

View File

@@ -34,6 +34,7 @@ import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.event.listener.Listener;
import org.apache.guacamole.resource.Resource; import org.apache.guacamole.resource.Resource;
import org.apache.guacamole.resource.ResourceServlet; import org.apache.guacamole.resource.ResourceServlet;
import org.apache.guacamole.resource.SequenceResource; import org.apache.guacamole.resource.SequenceResource;
@@ -91,6 +92,12 @@ public class ExtensionModule extends ServletModule {
private final List<AuthenticationProvider> boundAuthenticationProviders = private final List<AuthenticationProvider> boundAuthenticationProviders =
new ArrayList<AuthenticationProvider>(); new ArrayList<AuthenticationProvider>();
/**
* All currently-bound authentication providers, if any.
*/
private final List<ListenerProvider> boundListenerProviders =
new ArrayList<ListenerProvider>();
/** /**
* Service for adding and retrieving language resources. * Service for adding and retrieving language resources.
*/ */
@@ -187,6 +194,49 @@ public class ExtensionModule extends ServletModule {
return Collections.unmodifiableList(boundAuthenticationProviders); return Collections.unmodifiableList(boundAuthenticationProviders);
} }
/**
* Binds the given Listener class such that any service
* requiring access to the Listener can obtain it via
* injection, along with any other bound Listener.
*
* @param listenerClass
* The Listener class to bind.
*/
private void bindListenerProvider(Class<? extends Listener> listenerClass) {
// Bind listener
logger.debug("[{}] Binding Listener \"{}\".",
boundListenerProviders.size(), listenerClass.getName());
boundListenerProviders.add(new ListenerFacade(listenerClass));
}
/**
* Binds each of the the given Listener classes such that any
* service requiring access to the Listener can obtain it via
* injection.
*
* @param listeners
* The Listener classes to bind.
*/
private void bindListenerProviders(Collection<Class<Listener>> listeners) {
// Bind each listener within extension
for (Class<Listener> listener : listeners)
bindListenerProvider(listener);
}
/**
* Returns a list of all currently-bound ListenerProvider instances.
*
* @return
* A List of all currently-bound ListenerProvider instances. The List is
* not modifiable.
*/
@Provides
public List<ListenerProvider> getListenerProviders() {
return Collections.unmodifiableList(boundListenerProviders);
}
/** /**
* Serves each of the given resources as a language resource. Language * Serves each of the given resources as a language resource. Language
* resources are served from within the "/translations" directory as JSON * resources are served from within the "/translations" directory as JSON
@@ -327,6 +377,9 @@ public class ExtensionModule extends ServletModule {
// Attempt to load all authentication providers // Attempt to load all authentication providers
bindAuthenticationProviders(extension.getAuthenticationProviderClasses()); bindAuthenticationProviders(extension.getAuthenticationProviderClasses());
// Attempt to load all listeners
bindListenerProviders(extension.getListenerClasses());
// Add any translation resources // Add any translation resources
serveLanguageResources(extension.getTranslationResources()); serveLanguageResources(extension.getTranslationResources());