You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by jm...@apache.org on 2016/03/29 06:20:24 UTC

[15/51] [abbrv] incubator-guacamole-client git commit: GUACAMOLE-1: Remove useless .net.basic subpackage, now that everything is being renamed.

GUACAMOLE-1: Remove useless .net.basic subpackage, now that everything is being renamed.


Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/commit/648a6c96
Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/tree/648a6c96
Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/diff/648a6c96

Branch: refs/heads/master
Commit: 648a6c96f02973e8792af10328f771aeabd1a024
Parents: 4198c33
Author: Michael Jumper <mj...@apache.org>
Authored: Tue Mar 22 14:35:36 2016 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Mon Mar 28 20:49:56 2016 -0700

----------------------------------------------------------------------
 .../guacamole/BasicGuacamoleTunnelServlet.java  |  67 ++
 .../guacamole/BasicServletContextListener.java  | 103 +++
 .../org/apache/guacamole/ClipboardState.java    | 154 +++++
 .../org/apache/guacamole/EnvironmentModule.java |  60 ++
 .../apache/guacamole/GuacamoleClassLoader.java  | 185 ++++++
 .../org/apache/guacamole/GuacamoleSession.java  | 222 +++++++
 .../org/apache/guacamole/HTTPTunnelRequest.java |  90 +++
 .../java/org/apache/guacamole/TunnelLoader.java |  44 ++
 .../java/org/apache/guacamole/TunnelModule.java | 113 ++++
 .../org/apache/guacamole/TunnelRequest.java     | 372 +++++++++++
 .../apache/guacamole/TunnelRequestService.java  | 359 ++++++++++
 .../apache/guacamole/auth/Authorization.java    | 255 ++++++++
 .../org/apache/guacamole/auth/UserMapping.java  |  62 ++
 .../auth/basic/AuthorizeTagHandler.java         | 151 +++++
 .../basic/BasicFileAuthenticationProvider.java  | 218 +++++++
 .../auth/basic/ConnectionTagHandler.java        | 110 ++++
 .../guacamole/auth/basic/ParamTagHandler.java   |  74 +++
 .../auth/basic/ProtocolTagHandler.java          |  70 ++
 .../auth/basic/UserMappingTagHandler.java       |  78 +++
 .../guacamole/auth/basic/package-info.java      |  27 +
 .../org/apache/guacamole/auth/package-info.java |  28 +
 .../extension/AuthenticationProviderFacade.java | 203 ++++++
 .../extension/DirectoryClassLoader.java         | 154 +++++
 .../apache/guacamole/extension/Extension.java   | 515 +++++++++++++++
 .../guacamole/extension/ExtensionManifest.java  | 407 ++++++++++++
 .../guacamole/extension/ExtensionModule.java    | 450 +++++++++++++
 .../extension/LanguageResourceService.java      | 442 +++++++++++++
 .../extension/PatchResourceService.java         |  84 +++
 .../guacamole/extension/package-info.java       |  27 +
 .../org/apache/guacamole/log/LogModule.java     |  99 +++
 .../basic/BasicFileAuthenticationProvider.java  | 218 -------
 .../net/basic/BasicGuacamoleTunnelServlet.java  |  67 --
 .../net/basic/BasicServletContextListener.java  | 103 ---
 .../guacamole/net/basic/ClipboardState.java     | 154 -----
 .../guacamole/net/basic/EnvironmentModule.java  |  60 --
 .../net/basic/GuacamoleClassLoader.java         | 185 ------
 .../guacamole/net/basic/GuacamoleSession.java   | 222 -------
 .../guacamole/net/basic/HTTPTunnelRequest.java  |  90 ---
 .../guacamole/net/basic/TunnelLoader.java       |  44 --
 .../guacamole/net/basic/TunnelModule.java       | 113 ----
 .../guacamole/net/basic/TunnelRequest.java      | 372 -----------
 .../net/basic/TunnelRequestService.java         | 359 ----------
 .../guacamole/net/basic/auth/Authorization.java | 255 --------
 .../guacamole/net/basic/auth/UserMapping.java   |  62 --
 .../guacamole/net/basic/auth/package-info.java  |  28 -
 .../extension/AuthenticationProviderFacade.java | 203 ------
 .../basic/extension/DirectoryClassLoader.java   | 154 -----
 .../net/basic/extension/Extension.java          | 515 ---------------
 .../net/basic/extension/ExtensionManifest.java  | 407 ------------
 .../net/basic/extension/ExtensionModule.java    | 450 -------------
 .../extension/LanguageResourceService.java      | 442 -------------
 .../basic/extension/PatchResourceService.java   |  84 ---
 .../net/basic/extension/package-info.java       |  27 -
 .../guacamole/net/basic/log/LogModule.java      |  99 ---
 .../guacamole/net/basic/package-info.java       |  28 -
 .../AuthenticationProviderProperty.java         |  68 --
 .../properties/BasicGuacamoleProperties.java    |  90 ---
 .../net/basic/properties/StringSetProperty.java |  66 --
 .../net/basic/properties/package-info.java      |  28 -
 .../net/basic/resource/AbstractResource.java    |  82 ---
 .../net/basic/resource/ByteArrayResource.java   |  62 --
 .../net/basic/resource/ClassPathResource.java   |  85 ---
 .../guacamole/net/basic/resource/Resource.java  |  67 --
 .../net/basic/resource/ResourceServlet.java     | 126 ----
 .../net/basic/resource/SequenceResource.java    | 153 -----
 .../basic/resource/WebApplicationResource.java  | 116 ----
 .../net/basic/resource/package-info.java        |  28 -
 .../guacamole/net/basic/rest/APIError.java      | 183 ------
 .../guacamole/net/basic/rest/APIException.java  |  85 ---
 .../guacamole/net/basic/rest/APIPatch.java      | 104 ---
 .../guacamole/net/basic/rest/APIRequest.java    | 107 ---
 .../net/basic/rest/ObjectRetrievalService.java  | 280 --------
 .../apache/guacamole/net/basic/rest/PATCH.java  |  40 --
 .../net/basic/rest/RESTExceptionWrapper.java    | 274 --------
 .../net/basic/rest/RESTMethodMatcher.java       | 109 ----
 .../net/basic/rest/RESTServiceModule.java       | 107 ---
 .../activeconnection/APIActiveConnection.java   | 130 ----
 .../ActiveConnectionRESTService.java            | 196 ------
 .../rest/auth/APIAuthenticationResponse.java    | 105 ---
 .../rest/auth/APIAuthenticationResult.java      | 133 ----
 .../net/basic/rest/auth/AuthTokenGenerator.java |  39 --
 .../basic/rest/auth/AuthenticationService.java  | 475 --------------
 .../basic/rest/auth/BasicTokenSessionMap.java   | 189 ------
 .../auth/SecureRandomAuthTokenGenerator.java    |  48 --
 .../net/basic/rest/auth/TokenRESTService.java   | 230 -------
 .../net/basic/rest/auth/TokenSessionMap.java    |  69 --
 .../net/basic/rest/auth/package-info.java       |  27 -
 .../basic/rest/connection/APIConnection.java    | 233 -------
 .../rest/connection/APIConnectionWrapper.java   | 138 ----
 .../rest/connection/ConnectionRESTService.java  | 349 ----------
 .../net/basic/rest/connection/package-info.java |  27 -
 .../connectiongroup/APIConnectionGroup.java     | 272 --------
 .../APIConnectionGroupWrapper.java              | 124 ----
 .../ConnectionGroupRESTService.java             | 287 --------
 .../connectiongroup/ConnectionGroupTree.java    | 259 --------
 .../rest/connectiongroup/package-info.java      |  28 -
 .../basic/rest/history/APIConnectionRecord.java | 163 -----
 .../APIConnectionRecordSortPredicate.java       | 148 -----
 .../basic/rest/history/HistoryRESTService.java  | 147 -----
 .../net/basic/rest/history/package-info.java    |  28 -
 .../rest/language/LanguageRESTService.java      |  63 --
 .../net/basic/rest/language/package-info.java   |  27 -
 .../guacamole/net/basic/rest/package-info.java  |  27 -
 .../net/basic/rest/patch/PatchRESTService.java  | 133 ----
 .../net/basic/rest/patch/package-info.java      |  27 -
 .../basic/rest/permission/APIPermissionSet.java | 300 ---------
 .../net/basic/rest/permission/package-info.java |  27 -
 .../basic/rest/schema/SchemaRESTService.java    | 199 ------
 .../net/basic/rest/schema/package-info.java     |  27 -
 .../guacamole/net/basic/rest/user/APIUser.java  | 130 ----
 .../basic/rest/user/APIUserPasswordUpdate.java  |  82 ---
 .../net/basic/rest/user/APIUserWrapper.java     | 115 ----
 .../net/basic/rest/user/PermissionSetPatch.java |  98 ---
 .../net/basic/rest/user/UserRESTService.java    | 647 -------------------
 .../net/basic/rest/user/package-info.java       |  27 -
 .../BasicGuacamoleWebSocketTunnelEndpoint.java  | 120 ----
 .../basic/websocket/WebSocketTunnelModule.java  | 104 ---
 .../basic/websocket/WebSocketTunnelRequest.java |  70 --
 .../BasicGuacamoleWebSocketTunnelServlet.java   |  52 --
 .../jetty8/GuacamoleWebSocketTunnelServlet.java | 232 -------
 .../websocket/jetty8/WebSocketTunnelModule.java |  73 ---
 .../basic/websocket/jetty8/package-info.java    |  27 -
 .../jetty9/BasicGuacamoleWebSocketCreator.java  |  72 ---
 .../BasicGuacamoleWebSocketTunnelListener.java  |  59 --
 .../BasicGuacamoleWebSocketTunnelServlet.java   |  54 --
 .../GuacamoleWebSocketTunnelListener.java       | 243 -------
 .../websocket/jetty9/WebSocketTunnelModule.java |  73 ---
 .../jetty9/WebSocketTunnelRequest.java          |  76 ---
 .../basic/websocket/jetty9/package-info.java    |  28 -
 .../net/basic/websocket/package-info.java       |  28 -
 .../BasicGuacamoleWebSocketTunnelServlet.java   |  52 --
 .../tomcat/GuacamoleWebSocketTunnelServlet.java | 265 --------
 .../websocket/tomcat/WebSocketTunnelModule.java |  73 ---
 .../basic/websocket/tomcat/package-info.java    |  29 -
 .../xml/usermapping/AuthorizeTagHandler.java    | 151 -----
 .../xml/usermapping/ConnectionTagHandler.java   | 110 ----
 .../basic/xml/usermapping/ParamTagHandler.java  |  74 ---
 .../xml/usermapping/ProtocolTagHandler.java     |  70 --
 .../xml/usermapping/UserMappingTagHandler.java  |  78 ---
 .../net/basic/xml/usermapping/package-info.java |  27 -
 .../java/org/apache/guacamole/package-info.java |  28 +
 .../AuthenticationProviderProperty.java         |  68 ++
 .../properties/BasicGuacamoleProperties.java    |  90 +++
 .../guacamole/properties/StringSetProperty.java |  66 ++
 .../guacamole/properties/package-info.java      |  28 +
 .../guacamole/resource/AbstractResource.java    |  82 +++
 .../guacamole/resource/ByteArrayResource.java   |  62 ++
 .../guacamole/resource/ClassPathResource.java   |  85 +++
 .../org/apache/guacamole/resource/Resource.java |  67 ++
 .../guacamole/resource/ResourceServlet.java     | 126 ++++
 .../guacamole/resource/SequenceResource.java    | 153 +++++
 .../resource/WebApplicationResource.java        | 116 ++++
 .../apache/guacamole/resource/package-info.java |  28 +
 .../org/apache/guacamole/rest/APIError.java     | 183 ++++++
 .../org/apache/guacamole/rest/APIException.java |  85 +++
 .../org/apache/guacamole/rest/APIPatch.java     | 104 +++
 .../org/apache/guacamole/rest/APIRequest.java   | 107 +++
 .../guacamole/rest/ObjectRetrievalService.java  | 280 ++++++++
 .../java/org/apache/guacamole/rest/PATCH.java   |  40 ++
 .../guacamole/rest/RESTExceptionWrapper.java    | 274 ++++++++
 .../guacamole/rest/RESTMethodMatcher.java       | 109 ++++
 .../guacamole/rest/RESTServiceModule.java       | 107 +++
 .../activeconnection/APIActiveConnection.java   | 130 ++++
 .../ActiveConnectionRESTService.java            | 196 ++++++
 .../rest/auth/APIAuthenticationResponse.java    | 105 +++
 .../rest/auth/APIAuthenticationResult.java      | 133 ++++
 .../guacamole/rest/auth/AuthTokenGenerator.java |  39 ++
 .../rest/auth/AuthenticationService.java        | 475 ++++++++++++++
 .../rest/auth/BasicTokenSessionMap.java         | 189 ++++++
 .../auth/SecureRandomAuthTokenGenerator.java    |  48 ++
 .../guacamole/rest/auth/TokenRESTService.java   | 230 +++++++
 .../guacamole/rest/auth/TokenSessionMap.java    |  69 ++
 .../guacamole/rest/auth/package-info.java       |  27 +
 .../rest/connection/APIConnection.java          | 233 +++++++
 .../rest/connection/APIConnectionWrapper.java   | 138 ++++
 .../rest/connection/ConnectionRESTService.java  | 349 ++++++++++
 .../guacamole/rest/connection/package-info.java |  27 +
 .../connectiongroup/APIConnectionGroup.java     | 272 ++++++++
 .../APIConnectionGroupWrapper.java              | 124 ++++
 .../ConnectionGroupRESTService.java             | 287 ++++++++
 .../connectiongroup/ConnectionGroupTree.java    | 259 ++++++++
 .../rest/connectiongroup/package-info.java      |  28 +
 .../rest/history/APIConnectionRecord.java       | 163 +++++
 .../APIConnectionRecordSortPredicate.java       | 148 +++++
 .../rest/history/HistoryRESTService.java        | 147 +++++
 .../guacamole/rest/history/package-info.java    |  28 +
 .../rest/language/LanguageRESTService.java      |  63 ++
 .../guacamole/rest/language/package-info.java   |  27 +
 .../org/apache/guacamole/rest/package-info.java |  27 +
 .../guacamole/rest/patch/PatchRESTService.java  | 133 ++++
 .../guacamole/rest/patch/package-info.java      |  27 +
 .../rest/permission/APIPermissionSet.java       | 300 +++++++++
 .../guacamole/rest/permission/package-info.java |  27 +
 .../rest/schema/SchemaRESTService.java          | 199 ++++++
 .../guacamole/rest/schema/package-info.java     |  27 +
 .../org/apache/guacamole/rest/user/APIUser.java | 130 ++++
 .../rest/user/APIUserPasswordUpdate.java        |  82 +++
 .../guacamole/rest/user/APIUserWrapper.java     | 115 ++++
 .../guacamole/rest/user/PermissionSetPatch.java |  98 +++
 .../guacamole/rest/user/UserRESTService.java    | 647 +++++++++++++++++++
 .../guacamole/rest/user/package-info.java       |  27 +
 .../BasicGuacamoleWebSocketTunnelEndpoint.java  | 120 ++++
 .../websocket/WebSocketTunnelModule.java        | 104 +++
 .../websocket/WebSocketTunnelRequest.java       |  70 ++
 .../BasicGuacamoleWebSocketTunnelServlet.java   |  52 ++
 .../jetty8/GuacamoleWebSocketTunnelServlet.java | 232 +++++++
 .../websocket/jetty8/WebSocketTunnelModule.java |  73 +++
 .../websocket/jetty8/package-info.java          |  27 +
 .../jetty9/BasicGuacamoleWebSocketCreator.java  |  72 +++
 .../BasicGuacamoleWebSocketTunnelListener.java  |  59 ++
 .../BasicGuacamoleWebSocketTunnelServlet.java   |  54 ++
 .../GuacamoleWebSocketTunnelListener.java       | 243 +++++++
 .../websocket/jetty9/WebSocketTunnelModule.java |  73 +++
 .../jetty9/WebSocketTunnelRequest.java          |  76 +++
 .../websocket/jetty9/package-info.java          |  28 +
 .../guacamole/websocket/package-info.java       |  28 +
 .../BasicGuacamoleWebSocketTunnelServlet.java   |  52 ++
 .../tomcat/GuacamoleWebSocketTunnelServlet.java | 265 ++++++++
 .../websocket/tomcat/WebSocketTunnelModule.java |  73 +++
 .../websocket/tomcat/package-info.java          |  29 +
 guacamole/src/main/webapp/WEB-INF/web.xml       |   2 +-
 221 files changed, 15015 insertions(+), 15015 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/BasicGuacamoleTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/BasicGuacamoleTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/BasicGuacamoleTunnelServlet.java
new file mode 100644
index 0000000..7ebbd7f
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/BasicGuacamoleTunnelServlet.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Connects users to a tunnel associated with the authorized connection
+ * having the given ID.
+ *
+ * @author Michael Jumper
+ */
+@Singleton
+public class BasicGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet {
+
+    /**
+     * Service for handling tunnel requests.
+     */
+    @Inject
+    private TunnelRequestService tunnelRequestService;
+    
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(BasicGuacamoleTunnelServlet.class);
+
+    @Override
+    protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
+
+        // Attempt to create HTTP tunnel
+        GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
+
+        // If successful, warn of lack of WebSocket
+        logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.");
+
+        return tunnel;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/BasicServletContextListener.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/BasicServletContextListener.java b/guacamole/src/main/java/org/apache/guacamole/BasicServletContextListener.java
new file mode 100644
index 0000000..96fab85
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/BasicServletContextListener.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.google.inject.servlet.GuiceServletContextListener;
+import javax.servlet.ServletContextEvent;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.environment.Environment;
+import org.apache.guacamole.environment.LocalEnvironment;
+import org.apache.guacamole.extension.ExtensionModule;
+import org.apache.guacamole.log.LogModule;
+import org.apache.guacamole.rest.RESTServiceModule;
+import org.apache.guacamole.rest.auth.BasicTokenSessionMap;
+import org.apache.guacamole.rest.auth.TokenSessionMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A ServletContextListener to listen for initialization of the servlet context
+ * in order to set up dependency injection.
+ *
+ * @author James Muehlner
+ */
+public class BasicServletContextListener extends GuiceServletContextListener {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(BasicServletContextListener.class);
+
+    /**
+     * The Guacamole server environment.
+     */
+    private Environment environment;
+
+    /**
+     * Singleton instance of a TokenSessionMap.
+     */
+    private TokenSessionMap sessionMap;
+
+    @Override
+    public void contextInitialized(ServletContextEvent servletContextEvent) {
+
+        try {
+            environment = new LocalEnvironment();
+            sessionMap = new BasicTokenSessionMap(environment);
+        }
+        catch (GuacamoleException e) {
+            logger.error("Unable to read guacamole.properties: {}", e.getMessage());
+            logger.debug("Error reading guacamole.properties.", e);
+            throw new RuntimeException(e);
+        }
+
+        super.contextInitialized(servletContextEvent);
+
+    }
+
+    @Override
+    protected Injector getInjector() {
+        return Guice.createInjector(Stage.PRODUCTION,
+            new EnvironmentModule(environment),
+            new LogModule(environment),
+            new ExtensionModule(environment),
+            new RESTServiceModule(sessionMap),
+            new TunnelModule()
+        );
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent servletContextEvent) {
+
+        super.contextDestroyed(servletContextEvent);
+
+        // Shutdown TokenSessionMap
+        if (sessionMap != null)
+            sessionMap.shutdown();
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/ClipboardState.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/ClipboardState.java b/guacamole/src/main/java/org/apache/guacamole/ClipboardState.java
new file mode 100644
index 0000000..bde6822
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/ClipboardState.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+/**
+ * Provides central storage for a cross-connection clipboard state. This
+ * clipboard state is shared only for a single HTTP session. Multiple HTTP
+ * sessions will all have their own state.
+ * 
+ * @author Michael Jumper
+ */
+public class ClipboardState {
+
+    /**
+     * The maximum number of bytes to track.
+     */
+    private static final int MAXIMUM_LENGTH = 262144;
+
+     /**
+     * The mimetype of the current contents.
+     */
+    private String mimetype = "text/plain";
+
+    /**
+     * The mimetype of the pending contents.
+     */
+    private String pending_mimetype = "text/plain";
+    
+    /**
+     * The current contents.
+     */
+    private byte[] contents = new byte[0];
+
+    /**
+     * The pending clipboard contents.
+     */
+    private final byte[] pending = new byte[MAXIMUM_LENGTH];
+
+    /**
+     * The length of the pending data, in bytes.
+     */
+    private int pending_length = 0;
+    
+    /**
+     * The timestamp of the last contents update.
+     */
+    private long last_update = 0;
+    
+    /**
+     * Returns the current clipboard contents.
+     * @return The current clipboard contents
+     */
+    public synchronized byte[] getContents() {
+        return contents;
+    }
+
+    /**
+     * Returns the mimetype of the current clipboard contents.
+     * @return The mimetype of the current clipboard contents.
+     */
+    public synchronized String getMimetype() {
+        return mimetype;
+    }
+
+    /**
+     * Begins a new update of the clipboard contents. The actual contents will
+     * not be saved until commit() is called.
+     * 
+     * @param mimetype The mimetype of the contents being added.
+     */
+    public synchronized void begin(String mimetype) {
+        pending_length = 0;
+        this.pending_mimetype = mimetype;
+    }
+
+    /**
+     * Appends the given data to the clipboard contents.
+     * 
+     * @param data The raw data to append.
+     */
+    public synchronized void append(byte[] data) {
+
+        // Calculate size of copy
+        int length = data.length;
+        int remaining = pending.length - pending_length;
+        if (remaining < length)
+            length = remaining;
+    
+        // Append data
+        System.arraycopy(data, 0, pending, pending_length, length);
+        pending_length += length;
+
+    }
+
+    /**
+     * Commits the pending contents to the clipboard, notifying any threads
+     * waiting for clipboard updates.
+     */
+    public synchronized void commit() {
+
+        // Commit contents
+        mimetype = pending_mimetype;
+        contents = new byte[pending_length];
+        System.arraycopy(pending, 0, contents, 0, pending_length);
+
+        // Notify of update
+        last_update = System.currentTimeMillis();
+        this.notifyAll();
+
+    }
+    
+    /**
+     * Wait up to the given timeout for new clipboard data.
+     * 
+     * @param timeout The amount of time to wait, in milliseconds.
+     * @return true if the contents were updated within the timeframe given,
+     *         false otherwise.
+     */
+    public synchronized boolean waitForContents(int timeout) {
+
+        // Wait for new contents if it's been a while
+        if (System.currentTimeMillis() - last_update > timeout) {
+            try {
+                this.wait(timeout);
+                return true;
+            }
+            catch (InterruptedException e) { /* ignore */ }
+        }
+
+        return false;
+
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/EnvironmentModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/EnvironmentModule.java b/guacamole/src/main/java/org/apache/guacamole/EnvironmentModule.java
new file mode 100644
index 0000000..c4a6115
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/EnvironmentModule.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import com.google.inject.AbstractModule;
+import org.apache.guacamole.environment.Environment;
+
+/**
+ * Guice module which binds the base Guacamole server environment.
+ *
+ * @author Michael Jumper
+ */
+public class EnvironmentModule extends AbstractModule {
+
+    /**
+     * The Guacamole server environment.
+     */
+    private final Environment environment;
+
+    /**
+     * Creates a new EnvironmentModule which will bind the given environment
+     * for future injection.
+     *
+     * @param environment
+     *     The environment to bind.
+     */
+    public EnvironmentModule(Environment environment) {
+        this.environment = environment;
+    }
+
+    @Override
+    protected void configure() {
+
+        // Bind environment
+        bind(Environment.class).toInstance(environment);
+
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/GuacamoleClassLoader.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleClassLoader.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleClassLoader.java
new file mode 100644
index 0000000..f89b885
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleClassLoader.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.environment.Environment;
+import org.apache.guacamole.environment.LocalEnvironment;
+import org.apache.guacamole.properties.BasicGuacamoleProperties;
+
+/**
+ * A ClassLoader implementation which finds classes within a configurable
+ * directory. This directory is set within guacamole.properties. This class
+ * is deprecated in favor of DirectoryClassLoader, which is automatically
+ * configured based on the presence/absence of GUACAMOLE_HOME/lib.
+ *
+ * @author Michael Jumper
+ */
+@Deprecated
+public class GuacamoleClassLoader extends ClassLoader {
+
+    /**
+     * Class loader which will load classes from the classpath specified
+     * in guacamole.properties.
+     */
+    private URLClassLoader classLoader = null;
+
+    /**
+     * Any exception that occurs while the class loader is being instantiated.
+     */
+    private static GuacamoleException exception = null;
+
+    /**
+     * Singleton instance of the GuacamoleClassLoader.
+     */
+    private static GuacamoleClassLoader instance = null;
+
+    static {
+
+        try {
+            // Attempt to create singleton classloader which loads classes from
+            // all .jar's in the lib directory defined in guacamole.properties
+            instance = AccessController.doPrivileged(new PrivilegedExceptionAction<GuacamoleClassLoader>() {
+
+                @Override
+                public GuacamoleClassLoader run() throws GuacamoleException {
+
+                    // TODONT: This should be injected, but GuacamoleClassLoader will be removed soon.
+                    Environment environment = new LocalEnvironment();
+                    
+                    return new GuacamoleClassLoader(
+                        environment.getProperty(BasicGuacamoleProperties.LIB_DIRECTORY)
+                    );
+
+                }
+
+            });
+        }
+
+        catch (PrivilegedActionException e) {
+            // On error, record exception
+            exception = (GuacamoleException) e.getException();
+        }
+
+    }
+
+    /**
+     * Creates a new GuacamoleClassLoader which reads classes from the given
+     * directory.
+     *
+     * @param libDirectory The directory to load classes from.
+     * @throws GuacamoleException If the file given is not a director, or if
+     *                            an error occurs while constructing the URL
+     *                            for the backing classloader.
+     */
+    private GuacamoleClassLoader(File libDirectory) throws GuacamoleException {
+
+        // If no directory provided, just direct requests to parent classloader
+        if (libDirectory == null)
+            return;
+
+        // Validate directory is indeed a directory
+        if (!libDirectory.isDirectory())
+            throw new GuacamoleException(libDirectory + " is not a directory.");
+
+        // Get list of URLs for all .jar's in the lib directory
+        Collection<URL> jarURLs = new ArrayList<URL>();
+        File[] files = libDirectory.listFiles(new FilenameFilter() {
+
+            @Override
+            public boolean accept(File dir, String name) {
+
+                // If it ends with .jar, accept the file
+                return name.endsWith(".jar");
+
+            }
+
+        });
+
+        // Verify directory was successfully read
+        if (files == null)
+            throw new GuacamoleException("Unable to read contents of directory " + libDirectory);
+
+        // Add the URL for each .jar to the jar URL list
+        for (File file : files) {
+
+            try {
+                jarURLs.add(file.toURI().toURL());
+            }
+            catch (MalformedURLException e) {
+                throw new GuacamoleException(e);
+            }
+
+        }
+
+        // Set delegate classloader to new URLClassLoader which loads from the
+        // .jars found above.
+
+        URL[] urls = new URL[jarURLs.size()];
+        classLoader = new URLClassLoader(
+            jarURLs.toArray(urls),
+            getClass().getClassLoader()
+        );
+
+    }
+
+    /**
+     * Returns an instance of a GuacamoleClassLoader which finds classes
+     * within the directory configured in guacamole.properties.
+     *
+     * @return An instance of a GuacamoleClassLoader.
+     * @throws GuacamoleException If no instance could be returned due to an
+     *                            error.
+     */
+    public static GuacamoleClassLoader getInstance() throws GuacamoleException {
+
+        // If instance could not be created, rethrow original exception
+        if (exception != null) throw exception;
+
+        return instance;
+
+    }
+
+    @Override
+    public Class<?> findClass(String name) throws ClassNotFoundException {
+
+        // If no classloader, use default loader
+        if (classLoader == null)
+            return Class.forName(name);
+
+        // Otherwise, delegate
+        return classLoader.loadClass(name);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java
new file mode 100644
index 0000000..5345528
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.environment.Environment;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.net.auth.AuthenticatedUser;
+import org.apache.guacamole.net.auth.UserContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Contains Guacamole-specific user information which is tied to the current
+ * session, such as the UserContext and current clipboard state.
+ *
+ * @author Michael Jumper
+ */
+public class GuacamoleSession {
+
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(GuacamoleSession.class);
+
+    /**
+     * The user associated with this session.
+     */
+    private AuthenticatedUser authenticatedUser;
+    
+    /**
+     * All UserContexts associated with this session. Each
+     * AuthenticationProvider may provide its own UserContext.
+     */
+    private List<UserContext> userContexts;
+
+    /**
+     * All currently-active tunnels, indexed by tunnel UUID.
+     */
+    private final Map<String, GuacamoleTunnel> tunnels = new ConcurrentHashMap<String, GuacamoleTunnel>();
+
+    /**
+     * The last time this session was accessed.
+     */
+    private long lastAccessedTime;
+    
+    /**
+     * Creates a new Guacamole session associated with the given
+     * AuthenticatedUser and UserContexts.
+     *
+     * @param environment
+     *     The environment of the Guacamole server associated with this new
+     *     session.
+     *
+     * @param authenticatedUser
+     *     The authenticated user to associate this session with.
+     *
+     * @param userContexts
+     *     The List of UserContexts to associate with this session.
+     *
+     * @throws GuacamoleException
+     *     If an error prevents the session from being created.
+     */
+    public GuacamoleSession(Environment environment,
+            AuthenticatedUser authenticatedUser,
+            List<UserContext> userContexts)
+            throws GuacamoleException {
+        this.lastAccessedTime = System.currentTimeMillis();
+        this.authenticatedUser = authenticatedUser;
+        this.userContexts = userContexts;
+    }
+
+    /**
+     * Returns the authenticated user associated with this session.
+     *
+     * @return
+     *     The authenticated user associated with this session.
+     */
+    public AuthenticatedUser getAuthenticatedUser() {
+        return authenticatedUser;
+    }
+
+    /**
+     * Replaces the authenticated user associated with this session with the
+     * given authenticated user.
+     *
+     * @param authenticatedUser
+     *     The authenticated user to associated with this session.
+     */
+    public void setAuthenticatedUser(AuthenticatedUser authenticatedUser) {
+        this.authenticatedUser = authenticatedUser;
+    }
+
+    /**
+     * Returns a list of all UserContexts associated with this session. Each
+     * AuthenticationProvider currently loaded by Guacamole may provide its own
+     * UserContext for any successfully-authenticated user.
+     *
+     * @return
+     *     An unmodifiable list of all UserContexts associated with this
+     *     session.
+     */
+    public List<UserContext> getUserContexts() {
+        return Collections.unmodifiableList(userContexts);
+    }
+
+    /**
+     * Replaces all UserContexts associated with this session with the given
+     * List of UserContexts.
+     *
+     * @param userContexts
+     *     The List of UserContexts to associate with this session.
+     */
+    public void setUserContexts(List<UserContext> userContexts) {
+        this.userContexts = userContexts;
+    }
+    
+    /**
+     * Returns whether this session has any associated active tunnels.
+     *
+     * @return true if this session has any associated active tunnels,
+     *         false otherwise.
+     */
+    public boolean hasTunnels() {
+        return !tunnels.isEmpty();
+    }
+
+    /**
+     * Returns a map of all active tunnels associated with this session, where
+     * each key is the String representation of the tunnel's UUID. Changes to
+     * this map immediately affect the set of tunnels associated with this
+     * session. A tunnel need not be present here to be used by the user
+     * associated with this session, but tunnels not in this set will not
+     * be taken into account when determining whether a session is in use.
+     *
+     * @return A map of all active tunnels associated with this session.
+     */
+    public Map<String, GuacamoleTunnel> getTunnels() {
+        return tunnels;
+    }
+
+    /**
+     * Associates the given tunnel with this session, such that it is taken
+     * into account when determining session activity.
+     *
+     * @param tunnel The tunnel to associate with this session.
+     */
+    public void addTunnel(GuacamoleTunnel tunnel) {
+        tunnels.put(tunnel.getUUID().toString(), tunnel);
+    }
+
+    /**
+     * Disassociates the tunnel having the given UUID from this session.
+     *
+     * @param uuid The UUID of the tunnel to disassociate from this session.
+     * @return true if the tunnel existed and was removed, false otherwise.
+     */
+    public boolean removeTunnel(String uuid) {
+        return tunnels.remove(uuid) != null;
+    }
+
+    /**
+     * Updates this session, marking it as accessed.
+     */
+    public void access() {
+        lastAccessedTime = System.currentTimeMillis();
+    }
+
+    /**
+     * Returns the time this session was last accessed, as the number of
+     * milliseconds since midnight January 1, 1970 GMT. Session access must
+     * be explicitly marked through calls to the access() function.
+     *
+     * @return The time this session was last accessed.
+     */
+    public long getLastAccessedTime() {
+        return lastAccessedTime;
+    }
+
+    /**
+     * Closes all associated tunnels and prevents any further use of this
+     * session.
+     */
+    public void invalidate() {
+
+        // Close all associated tunnels, if possible
+        for (GuacamoleTunnel tunnel : tunnels.values()) {
+            try {
+                tunnel.close();
+            }
+            catch (GuacamoleException e) {
+                logger.debug("Unable to close tunnel.", e);
+            }
+        }
+
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/HTTPTunnelRequest.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/HTTPTunnelRequest.java b/guacamole/src/main/java/org/apache/guacamole/HTTPTunnelRequest.java
new file mode 100644
index 0000000..9edc45a
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/HTTPTunnelRequest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * HTTP-specific implementation of TunnelRequest.
+ *
+ * @author Michael Jumper
+ */
+public class HTTPTunnelRequest extends TunnelRequest {
+
+    /**
+     * A copy of the parameters obtained from the HttpServletRequest used to
+     * construct the HTTPTunnelRequest.
+     */
+    private final Map<String, List<String>> parameterMap =
+            new HashMap<String, List<String>>();
+
+    /**
+     * Creates a HTTPTunnelRequest which copies and exposes the parameters
+     * from the given HttpServletRequest.
+     *
+     * @param request
+     *     The HttpServletRequest to copy parameter values from.
+     */
+    @SuppressWarnings("unchecked") // getParameterMap() is defined as returning Map<String, String[]>
+    public HTTPTunnelRequest(HttpServletRequest request) {
+
+        // For each parameter
+        for (Map.Entry<String, String[]> mapEntry : ((Map<String, String[]>)
+                request.getParameterMap()).entrySet()) {
+
+            // Get parameter name and corresponding values
+            String parameterName = mapEntry.getKey();
+            List<String> parameterValues = Arrays.asList(mapEntry.getValue());
+
+            // Store copy of all values in our own map
+            parameterMap.put(
+                parameterName,
+                new ArrayList<String>(parameterValues)
+            );
+
+        }
+
+    }
+
+    @Override
+    public String getParameter(String name) {
+        List<String> values = getParameterValues(name);
+
+        // Return the first value from the list if available
+        if (values != null && !values.isEmpty())
+            return values.get(0);
+
+        return null;
+    }
+
+    @Override
+    public List<String> getParameterValues(String name) {
+        return parameterMap.get(name);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/TunnelLoader.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/TunnelLoader.java b/guacamole/src/main/java/org/apache/guacamole/TunnelLoader.java
new file mode 100644
index 0000000..756c6dd
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/TunnelLoader.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import com.google.inject.Module;
+
+/**
+ * Generic means of loading a tunnel without adding explicit dependencies within
+ * the main ServletModule, as not all servlet containers may have the classes
+ * required by all tunnel implementations.
+ *
+ * @author Michael Jumper
+ */
+public interface TunnelLoader extends Module {
+
+    /**
+     * Checks whether this type of tunnel is supported by the servlet container.
+     * 
+     * @return true if this type of tunnel is supported and can be loaded
+     *         without errors, false otherwise.
+     */
+    public boolean isSupported();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/TunnelModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/TunnelModule.java b/guacamole/src/main/java/org/apache/guacamole/TunnelModule.java
new file mode 100644
index 0000000..e7c105c
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/TunnelModule.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import com.google.inject.servlet.ServletModule;
+import java.lang.reflect.InvocationTargetException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Module which loads tunnel implementations.
+ *
+ * @author Michael Jumper
+ */
+public class TunnelModule extends ServletModule {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(TunnelModule.class);
+
+    /**
+     * Classnames of all implementation-specific WebSocket tunnel modules.
+     */
+    private static final String[] WEBSOCKET_MODULES = {
+        "org.apache.guacamole.websocket.WebSocketTunnelModule",
+        "org.apache.guacamole.websocket.jetty8.WebSocketTunnelModule",
+        "org.apache.guacamole.websocket.jetty9.WebSocketTunnelModule",
+        "org.apache.guacamole.websocket.tomcat.WebSocketTunnelModule"
+    };
+
+    private boolean loadWebSocketModule(String classname) {
+
+        try {
+
+            // Attempt to find WebSocket module
+            Class<?> module = Class.forName(classname);
+
+            // Create loader
+            TunnelLoader loader = (TunnelLoader) module.getConstructor().newInstance();
+
+            // Install module, if supported
+            if (loader.isSupported()) {
+                install(loader);
+                return true;
+            }
+
+        }
+
+        // If no such class or constructor, etc., then this particular
+        // WebSocket support is not present
+        catch (ClassNotFoundException e) {}
+        catch (NoClassDefFoundError e) {}
+        catch (NoSuchMethodException e) {}
+
+        // Log errors which indicate bugs
+        catch (InstantiationException e) {
+            logger.debug("Error instantiating WebSocket module.", e);
+        }
+        catch (IllegalAccessException e) {
+            logger.debug("Error instantiating WebSocket module.", e);
+        }
+        catch (InvocationTargetException e) {
+            logger.debug("Error instantiating WebSocket module.", e);
+        }
+
+        // Load attempt failed
+        return false;
+
+    }
+
+    @Override
+    protected void configureServlets() {
+
+        bind(TunnelRequestService.class);
+
+        // Set up HTTP tunnel
+        serve("/tunnel").with(BasicGuacamoleTunnelServlet.class);
+
+        // Try to load each WebSocket tunnel in sequence
+        for (String classname : WEBSOCKET_MODULES) {
+            if (loadWebSocketModule(classname)) {
+                logger.debug("WebSocket module loaded: {}", classname);
+                return;
+            }
+        }
+
+        // Warn of lack of WebSocket
+        logger.info("WebSocket support NOT present. Only HTTP will be used.");
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/TunnelRequest.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/TunnelRequest.java b/guacamole/src/main/java/org/apache/guacamole/TunnelRequest.java
new file mode 100644
index 0000000..622e2f0
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/TunnelRequest.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2015 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import java.util.List;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleException;
+
+/**
+ * A request object which provides only the functions absolutely required to
+ * retrieve and connect to a tunnel.
+ *
+ * @author Michael Jumper
+ */
+public abstract class TunnelRequest {
+
+    /**
+     * The name of the request parameter containing the user's authentication
+     * token.
+     */
+    public static final String AUTH_TOKEN_PARAMETER = "token";
+
+    /**
+     * The name of the parameter containing the identifier of the
+     * AuthenticationProvider associated with the UserContext containing the
+     * object to which a tunnel is being requested.
+     */
+    public static final String AUTH_PROVIDER_IDENTIFIER_PARAMETER = "GUAC_DATA_SOURCE";
+
+    /**
+     * The name of the parameter specifying the type of object to which a
+     * tunnel is being requested. Currently, this may be "c" for a Guacamole
+     * connection, or "g" for a Guacamole connection group.
+     */
+    public static final String TYPE_PARAMETER = "GUAC_TYPE";
+
+    /**
+     * The name of the parameter containing the unique identifier of the object
+     * to which a tunnel is being requested.
+     */
+    public static final String IDENTIFIER_PARAMETER = "GUAC_ID";
+
+    /**
+     * The name of the parameter containing the desired display width, in
+     * pixels.
+     */
+    public static final String WIDTH_PARAMETER = "GUAC_WIDTH";
+
+    /**
+     * The name of the parameter containing the desired display height, in
+     * pixels.
+     */
+    public static final String HEIGHT_PARAMETER = "GUAC_HEIGHT";
+
+    /**
+     * The name of the parameter containing the desired display resolution, in
+     * DPI.
+     */
+    public static final String DPI_PARAMETER = "GUAC_DPI";
+
+    /**
+     * The name of the parameter specifying one supported audio mimetype. This
+     * will normally appear multiple times within a single tunnel request -
+     * once for each mimetype.
+     */
+    public static final String AUDIO_PARAMETER = "GUAC_AUDIO";
+
+    /**
+     * The name of the parameter specifying one supported video mimetype. This
+     * will normally appear multiple times within a single tunnel request -
+     * once for each mimetype.
+     */
+    public static final String VIDEO_PARAMETER = "GUAC_VIDEO";
+
+    /**
+     * The name of the parameter specifying one supported image mimetype. This
+     * will normally appear multiple times within a single tunnel request -
+     * once for each mimetype.
+     */
+    public static final String IMAGE_PARAMETER = "GUAC_IMAGE";
+
+    /**
+     * All supported object types that can be used as the destination of a
+     * tunnel.
+     */
+    public static enum Type {
+
+        /**
+         * A Guacamole connection.
+         */
+        CONNECTION("c"),
+
+        /**
+         * A Guacamole connection group.
+         */
+        CONNECTION_GROUP("g");
+
+        /**
+         * The parameter value which denotes a destination object of this type.
+         */
+        final String PARAMETER_VALUE;
+        
+        /**
+         * Defines a Type having the given corresponding parameter value.
+         *
+         * @param value
+         *     The parameter value which denotes a destination object of this
+         *     type.
+         */
+        Type(String value) {
+            PARAMETER_VALUE = value;
+        }
+
+    };
+
+    /**
+     * Returns the value of the parameter having the given name.
+     *
+     * @param name
+     *     The name of the parameter to return.
+     *
+     * @return
+     *     The value of the parameter having the given name, or null if no such
+     *     parameter was specified.
+     */
+    public abstract String getParameter(String name);
+
+    /**
+     * Returns a list of all values specified for the given parameter.
+     *
+     * @param name
+     *     The name of the parameter to return.
+     *
+     * @return
+     *     All values of the parameter having the given name , or null if no
+     *     such parameter was specified.
+     */
+    public abstract List<String> getParameterValues(String name);
+
+    /**
+     * Returns the value of the parameter having the given name, throwing an
+     * exception if the parameter is missing.
+     *
+     * @param name
+     *     The name of the parameter to return.
+     *
+     * @return
+     *     The value of the parameter having the given name.
+     *
+     * @throws GuacamoleException
+     *     If the parameter is not present in the request.
+     */
+    public String getRequiredParameter(String name) throws GuacamoleException {
+
+        // Pull requested parameter, aborting if absent
+        String value = getParameter(name);
+        if (value == null)
+            throw new GuacamoleClientException("Parameter \"" + name + "\" is required.");
+
+        return value;
+
+    }
+
+    /**
+     * Returns the integer value of the parameter having the given name,
+     * throwing an exception if the parameter cannot be parsed.
+     *
+     * @param name
+     *     The name of the parameter to return.
+     *
+     * @return
+     *     The integer value of the parameter having the given name, or null if
+     *     the parameter is missing.
+     *
+     * @throws GuacamoleException
+     *     If the parameter is not a valid integer.
+     */
+    public Integer getIntegerParameter(String name) throws GuacamoleException {
+
+        // Pull requested parameter
+        String value = getParameter(name);
+        if (value == null)
+            return null;
+
+        // Attempt to parse as an integer
+        try {
+            return Integer.parseInt(value);
+        }
+
+        // Rethrow any parsing error as a GuacamoleClientException
+        catch (NumberFormatException e) {
+            throw new GuacamoleClientException("Parameter \"" + name + "\" must be a valid integer.", e);
+        }
+
+    }
+
+    /**
+     * Returns the authentication token associated with this tunnel request.
+     *
+     * @return
+     *     The authentication token associated with this tunnel request, or
+     *     null if no authentication token is present.
+     */
+    public String getAuthenticationToken() {
+        return getParameter(AUTH_TOKEN_PARAMETER);
+    }
+
+    /**
+     * Returns the identifier of the AuthenticationProvider associated with the
+     * UserContext from which the connection or connection group is to be
+     * retrieved when the tunnel is created. In the context of the REST API and
+     * the JavaScript side of the web application, this is referred to as the
+     * data source identifier.
+     *
+     * @return
+     *     The identifier of the AuthenticationProvider associated with the
+     *     UserContext from which the connection or connection group is to be
+     *     retrieved when the tunnel is created.
+     *
+     * @throws GuacamoleException
+     *     If the identifier was not present in the request.
+     */
+    public String getAuthenticationProviderIdentifier()
+            throws GuacamoleException {
+        return getRequiredParameter(AUTH_PROVIDER_IDENTIFIER_PARAMETER);
+    }
+
+    /**
+     * Returns the type of object for which the tunnel is being requested.
+     *
+     * @return
+     *     The type of object for which the tunnel is being requested.
+     *
+     * @throws GuacamoleException
+     *     If the type was not present in the request, or if the type requested
+     *     is in the wrong format.
+     */
+    public Type getType() throws GuacamoleException {
+
+        String type = getRequiredParameter(TYPE_PARAMETER);
+
+        // For each possible object type
+        for (Type possibleType : Type.values()) {
+
+            // Match against defined parameter value
+            if (type.equals(possibleType.PARAMETER_VALUE))
+                return possibleType;
+
+        }
+
+        throw new GuacamoleClientException("Illegal identifier - unknown type.");
+
+    }
+
+    /**
+     * Returns the identifier of the destination of the tunnel being requested.
+     * As there are multiple types of destination objects available, and within
+     * multiple data sources, the associated object type and data source are
+     * also necessary to determine what this identifier refers to.
+     *
+     * @return
+     *     The identifier of the destination of the tunnel being requested.
+     *
+     * @throws GuacamoleException
+     *     If the identifier was not present in the request.
+     */
+    public String getIdentifier() throws GuacamoleException {
+        return getRequiredParameter(IDENTIFIER_PARAMETER);
+    }
+
+    /**
+     * Returns the display width desired for the Guacamole session over the
+     * tunnel being requested.
+     *
+     * @return
+     *     The display width desired for the Guacamole session over the tunnel
+     *     being requested, or null if no width was given.
+     *
+     * @throws GuacamoleException
+     *     If the width specified was not a valid integer.
+     */
+    public Integer getWidth() throws GuacamoleException {
+        return getIntegerParameter(WIDTH_PARAMETER);
+    }
+
+    /**
+     * Returns the display height desired for the Guacamole session over the
+     * tunnel being requested.
+     *
+     * @return
+     *     The display height desired for the Guacamole session over the tunnel
+     *     being requested, or null if no width was given.
+     *
+     * @throws GuacamoleException
+     *     If the height specified was not a valid integer.
+     */
+    public Integer getHeight() throws GuacamoleException {
+        return getIntegerParameter(HEIGHT_PARAMETER);
+    }
+
+    /**
+     * Returns the display resolution desired for the Guacamole session over
+     * the tunnel being requested, in DPI.
+     *
+     * @return
+     *     The display resolution desired for the Guacamole session over the
+     *     tunnel being requested, or null if no resolution was given.
+     *
+     * @throws GuacamoleException
+     *     If the resolution specified was not a valid integer.
+     */
+    public Integer getDPI() throws GuacamoleException {
+        return getIntegerParameter(DPI_PARAMETER);
+    }
+
+    /**
+     * Returns a list of all audio mimetypes declared as supported within the
+     * tunnel request.
+     *
+     * @return
+     *     A list of all audio mimetypes declared as supported within the
+     *     tunnel request, or null if no mimetypes were specified.
+     */
+    public List<String> getAudioMimetypes() {
+        return getParameterValues(AUDIO_PARAMETER);
+    }
+
+    /**
+     * Returns a list of all video mimetypes declared as supported within the
+     * tunnel request.
+     *
+     * @return
+     *     A list of all video mimetypes declared as supported within the
+     *     tunnel request, or null if no mimetypes were specified.
+     */
+    public List<String> getVideoMimetypes() {
+        return getParameterValues(VIDEO_PARAMETER);
+    }
+
+    /**
+     * Returns a list of all image mimetypes declared as supported within the
+     * tunnel request.
+     *
+     * @return
+     *     A list of all image mimetypes declared as supported within the
+     *     tunnel request, or null if no mimetypes were specified.
+     */
+    public List<String> getImageMimetypes() {
+        return getParameterValues(IMAGE_PARAMETER);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/TunnelRequestService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/TunnelRequestService.java b/guacamole/src/main/java/org/apache/guacamole/TunnelRequestService.java
new file mode 100644
index 0000000..d78fdde
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/TunnelRequestService.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2013 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.apache.guacamole;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.List;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleSecurityException;
+import org.apache.guacamole.GuacamoleUnauthorizedException;
+import org.apache.guacamole.net.DelegatingGuacamoleTunnel;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.net.auth.Connection;
+import org.apache.guacamole.net.auth.ConnectionGroup;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.rest.ObjectRetrievalService;
+import org.apache.guacamole.rest.auth.AuthenticationService;
+import org.apache.guacamole.protocol.GuacamoleClientInformation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class that takes a standard request from the Guacamole JavaScript
+ * client and produces the corresponding GuacamoleTunnel. The implementation
+ * of this utility is specific to the form of request used by the upstream
+ * Guacamole web application, and is not necessarily useful to applications
+ * that use purely the Guacamole API.
+ *
+ * @author Michael Jumper
+ * @author Vasily Loginov
+ */
+@Singleton
+public class TunnelRequestService {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class);
+
+    /**
+     * A service for authenticating users from auth tokens.
+     */
+    @Inject
+    private AuthenticationService authenticationService;
+
+    /**
+     * Service for convenient retrieval of objects.
+     */
+    @Inject
+    private ObjectRetrievalService retrievalService;
+
+    /**
+     * Reads and returns the client information provided within the given
+     * request.
+     *
+     * @param request
+     *     The request describing tunnel to create.
+     *
+     * @return GuacamoleClientInformation
+     *     An object containing information about the client sending the tunnel
+     *     request.
+     *
+     * @throws GuacamoleException
+     *     If the parameters of the tunnel request are invalid.
+     */
+    protected GuacamoleClientInformation getClientInformation(TunnelRequest request)
+        throws GuacamoleException {
+
+        // Get client information
+        GuacamoleClientInformation info = new GuacamoleClientInformation();
+
+        // Set width if provided
+        Integer width = request.getWidth();
+        if (width != null)
+            info.setOptimalScreenWidth(width);
+
+        // Set height if provided
+        Integer height = request.getHeight();
+        if (height != null)
+            info.setOptimalScreenHeight(height);
+
+        // Set resolution if provided
+        Integer dpi = request.getDPI();
+        if (dpi != null)
+            info.setOptimalResolution(dpi);
+
+        // Add audio mimetypes
+        List<String> audioMimetypes = request.getAudioMimetypes();
+        if (audioMimetypes != null)
+            info.getAudioMimetypes().addAll(audioMimetypes);
+
+        // Add video mimetypes
+        List<String> videoMimetypes = request.getVideoMimetypes();
+        if (videoMimetypes != null)
+            info.getVideoMimetypes().addAll(videoMimetypes);
+
+        // Add image mimetypes
+        List<String> imageMimetypes = request.getImageMimetypes();
+        if (imageMimetypes != null)
+            info.getImageMimetypes().addAll(imageMimetypes);
+
+        return info;
+    }
+
+    /**
+     * Creates a new tunnel using which is connected to the connection or
+     * connection group identifier by the given ID. Client information
+     * is specified in the {@code info} parameter.
+     *
+     * @param context
+     *     The UserContext associated with the user for whom the tunnel is
+     *     being created.
+     *
+     * @param type
+     *     The type of object being connected to (connection or group).
+     *
+     * @param id
+     *     The id of the connection or group being connected to.
+     *
+     * @param info
+     *     Information describing the connected Guacamole client.
+     *
+     * @return
+     *     A new tunnel, connected as required by the request.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while creating the tunnel.
+     */
+    protected GuacamoleTunnel createConnectedTunnel(UserContext context,
+            final TunnelRequest.Type type, String id,
+            GuacamoleClientInformation info)
+            throws GuacamoleException {
+
+        // Create connected tunnel from identifier
+        GuacamoleTunnel tunnel = null;
+        switch (type) {
+
+            // Connection identifiers
+            case CONNECTION: {
+
+                // Get connection directory
+                Directory<Connection> directory = context.getConnectionDirectory();
+
+                // Get authorized connection
+                Connection connection = directory.get(id);
+                if (connection == null) {
+                    logger.info("Connection \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier());
+                    throw new GuacamoleSecurityException("Requested connection is not authorized.");
+                }
+
+                // Connect tunnel
+                tunnel = connection.connect(info);
+                logger.info("User \"{}\" connected to connection \"{}\".", context.self().getIdentifier(), id);
+                break;
+            }
+
+            // Connection group identifiers
+            case CONNECTION_GROUP: {
+
+                // Get connection group directory
+                Directory<ConnectionGroup> directory = context.getConnectionGroupDirectory();
+
+                // Get authorized connection group
+                ConnectionGroup group = directory.get(id);
+                if (group == null) {
+                    logger.info("Connection group \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier());
+                    throw new GuacamoleSecurityException("Requested connection group is not authorized.");
+                }
+
+                // Connect tunnel
+                tunnel = group.connect(info);
+                logger.info("User \"{}\" connected to group \"{}\".", context.self().getIdentifier(), id);
+                break;
+            }
+
+            // Type is guaranteed to be one of the above
+            default:
+                assert(false);
+
+        }
+
+        return tunnel;
+
+    }
+
+    /**
+     * Associates the given tunnel with the given session, returning a wrapped
+     * version of the same tunnel which automatically handles closure and
+     * removal from the session.
+     *
+     * @param tunnel
+     *     The connected tunnel to wrap and monitor.
+     *
+     * @param authToken
+     *     The authentication token associated with the given session. If
+     *     provided, this token will be automatically invalidated (and the
+     *     corresponding session destroyed) if tunnel errors imply that the
+     *     user is no longer authorized.
+     *
+     * @param session
+     *     The Guacamole session to associate the tunnel with.
+     *
+     * @param type
+     *     The type of object being connected to (connection or group).
+     *
+     * @param id
+     *     The id of the connection or group being connected to.
+     *
+     * @return
+     *     A new tunnel, associated with the given session, which delegates all
+     *     functionality to the given tunnel while monitoring and automatically
+     *     handling closure.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while obtaining the tunnel.
+     */
+    protected GuacamoleTunnel createAssociatedTunnel(GuacamoleTunnel tunnel,
+            final String authToken,  final GuacamoleSession session,
+            final TunnelRequest.Type type, final String id)
+            throws GuacamoleException {
+
+        // Monitor tunnel closure and data
+        GuacamoleTunnel monitoredTunnel = new DelegatingGuacamoleTunnel(tunnel) {
+
+            /**
+             * The time the connection began, measured in milliseconds since
+             * midnight, January 1, 1970 UTC.
+             */
+            private final long connectionStartTime = System.currentTimeMillis();
+
+            @Override
+            public void close() throws GuacamoleException {
+
+                long connectionEndTime = System.currentTimeMillis();
+                long duration = connectionEndTime - connectionStartTime;
+
+                // Log closure
+                switch (type) {
+
+                    // Connection identifiers
+                    case CONNECTION:
+                        logger.info("User \"{}\" disconnected from connection \"{}\". Duration: {} milliseconds",
+                                session.getAuthenticatedUser().getIdentifier(), id, duration);
+                        break;
+
+                    // Connection group identifiers
+                    case CONNECTION_GROUP:
+                        logger.info("User \"{}\" disconnected from connection group \"{}\". Duration: {} milliseconds",
+                                session.getAuthenticatedUser().getIdentifier(), id, duration);
+                        break;
+
+                    // Type is guaranteed to be one of the above
+                    default:
+                        assert(false);
+
+                }
+
+                try {
+
+                    // Close and clean up tunnel
+                    session.removeTunnel(getUUID().toString());
+                    super.close();
+
+                }
+
+                // Ensure any associated session is invalidated if unauthorized
+                catch (GuacamoleUnauthorizedException e) {
+
+                    // If there is an associated auth token, invalidate it
+                    if (authenticationService.destroyGuacamoleSession(authToken))
+                        logger.debug("Implicitly invalidated session for token \"{}\".", authToken);
+
+                    // Continue with exception processing
+                    throw e;
+
+                }
+
+            }
+
+        };
+
+        // Associate tunnel with session
+        session.addTunnel(monitoredTunnel);
+        return monitoredTunnel;
+        
+    }
+
+    /**
+     * Creates a new tunnel using the parameters and credentials present in
+     * the given request.
+     *
+     * @param request
+     *     The request describing the tunnel to create.
+     *
+     * @return
+     *     The created tunnel, or null if the tunnel could not be created.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while creating the tunnel.
+     */
+    public GuacamoleTunnel createTunnel(TunnelRequest request)
+            throws GuacamoleException {
+
+        // Parse request parameters
+        String authToken                = request.getAuthenticationToken();
+        String id                       = request.getIdentifier();
+        TunnelRequest.Type type         = request.getType();
+        String authProviderIdentifier   = request.getAuthenticationProviderIdentifier();
+        GuacamoleClientInformation info = getClientInformation(request);
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+
+        try {
+
+            // Create connected tunnel using provided connection ID and client information
+            GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info);
+
+            // Associate tunnel with session
+            return createAssociatedTunnel(tunnel, authToken, session, type, id);
+
+        }
+
+        // Ensure any associated session is invalidated if unauthorized
+        catch (GuacamoleUnauthorizedException e) {
+
+            // If there is an associated auth token, invalidate it
+            if (authenticationService.destroyGuacamoleSession(authToken))
+                logger.debug("Implicitly invalidated session for token \"{}\".", authToken);
+
+            // Continue with exception processing
+            throw e;
+
+        }
+
+    }
+
+}