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:12 UTC

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

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionRESTService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionRESTService.java b/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionRESTService.java
new file mode 100644
index 0000000..b301082
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/connection/ConnectionRESTService.java
@@ -0,0 +1,349 @@
+/*
+ * 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.rest.connection;
+
+import com.google.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleSecurityException;
+import org.apache.guacamole.net.auth.Connection;
+import org.apache.guacamole.net.auth.ConnectionRecord;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.User;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.net.auth.permission.ObjectPermission;
+import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
+import org.apache.guacamole.net.auth.permission.SystemPermission;
+import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
+import org.apache.guacamole.GuacamoleSession;
+import org.apache.guacamole.rest.ObjectRetrievalService;
+import org.apache.guacamole.rest.auth.AuthenticationService;
+import org.apache.guacamole.rest.history.APIConnectionRecord;
+import org.apache.guacamole.protocol.GuacamoleConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A REST Service for handling connection CRUD operations.
+ * 
+ * @author James Muehlner
+ */
+@Path("/data/{dataSource}/connections")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class ConnectionRESTService {
+
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(ConnectionRESTService.class);
+
+    /**
+     * A service for authenticating users from auth tokens.
+     */
+    @Inject
+    private AuthenticationService authenticationService;
+    
+    /**
+     * Service for convenient retrieval of objects.
+     */
+    @Inject
+    private ObjectRetrievalService retrievalService;
+    
+    /**
+     * Retrieves an individual connection.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection to be retrieved.
+     *
+     * @param connectionID
+     *     The identifier of the connection to retrieve.
+     *
+     * @return
+     *     The connection having the given identifier.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while retrieving the connection.
+     */
+    @GET
+    @Path("/{connectionID}")
+    public APIConnection getConnection(@QueryParam("token") String authToken, 
+            @PathParam("dataSource") String authProviderIdentifier,
+            @PathParam("connectionID") String connectionID)
+            throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        
+        // Retrieve the requested connection
+        return new APIConnection(retrievalService.retrieveConnection(session, authProviderIdentifier, connectionID));
+
+    }
+
+    /**
+     * Retrieves the parameters associated with a single connection.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection whose parameters are to be
+     *     retrieved.
+     *
+     * @param connectionID
+     *     The identifier of the connection.
+     *
+     * @return
+     *     A map of parameter name/value pairs.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while retrieving the connection parameters.
+     */
+    @GET
+    @Path("/{connectionID}/parameters")
+    public Map<String, String> getConnectionParameters(@QueryParam("token") String authToken, 
+            @PathParam("dataSource") String authProviderIdentifier,
+            @PathParam("connectionID") String connectionID)
+            throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+        User self = userContext.self();
+
+        // Retrieve permission sets
+        SystemPermissionSet systemPermissions = self.getSystemPermissions();
+        ObjectPermissionSet connectionPermissions = self.getConnectionPermissions();
+
+        // Deny access if adminstrative or update permission is missing
+        if (!systemPermissions.hasPermission(SystemPermission.Type.ADMINISTER)
+         && !connectionPermissions.hasPermission(ObjectPermission.Type.UPDATE, connectionID))
+            throw new GuacamoleSecurityException("Permission to read connection parameters denied.");
+
+        // Retrieve the requested connection
+        Connection connection = retrievalService.retrieveConnection(userContext, connectionID);
+
+        // Retrieve connection configuration
+        GuacamoleConfiguration config = connection.getConfiguration();
+
+        // Return parameter map
+        return config.getParameters();
+
+    }
+
+    /**
+     * Retrieves the usage history of a single connection.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection whose history is to be
+     *     retrieved.
+     *
+     * @param connectionID
+     *     The identifier of the connection.
+     *
+     * @return
+     *     A list of connection records, describing the start and end times of
+     *     various usages of this connection.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while retrieving the connection history.
+     */
+    @GET
+    @Path("/{connectionID}/history")
+    public List<APIConnectionRecord> getConnectionHistory(@QueryParam("token") String authToken, 
+            @PathParam("dataSource") String authProviderIdentifier,
+            @PathParam("connectionID") String connectionID)
+            throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+
+        // Retrieve the requested connection
+        Connection connection = retrievalService.retrieveConnection(session, authProviderIdentifier, connectionID);
+
+        // Retrieve the requested connection's history
+        List<APIConnectionRecord> apiRecords = new ArrayList<APIConnectionRecord>();
+        for (ConnectionRecord record : connection.getHistory())
+            apiRecords.add(new APIConnectionRecord(record));
+
+        // Return the converted history
+        return apiRecords;
+
+    }
+
+    /**
+     * Deletes an individual connection.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection to be deleted.
+     *
+     * @param connectionID
+     *     The identifier of the connection to delete.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while deleting the connection.
+     */
+    @DELETE
+    @Path("/{connectionID}")
+    public void deleteConnection(@QueryParam("token") String authToken,
+            @PathParam("dataSource") String authProviderIdentifier,
+            @PathParam("connectionID") String connectionID)
+            throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+
+        // Get the connection directory
+        Directory<Connection> connectionDirectory = userContext.getConnectionDirectory();
+
+        // Delete the specified connection
+        connectionDirectory.remove(connectionID);
+
+    }
+
+    /**
+     * Creates a new connection and returns the new connection, with identifier
+     * field populated.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext in which the connection is to be created.
+     *
+     * @param connection
+     *     The connection to create.
+     *
+     * @return
+     *     The new connection.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while creating the connection.
+     */
+    @POST
+    public APIConnection createConnection(@QueryParam("token") String authToken,
+            @PathParam("dataSource") String authProviderIdentifier,
+            APIConnection connection) throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+        
+        // Validate that connection data was provided
+        if (connection == null)
+            throw new GuacamoleClientException("Connection JSON must be submitted when creating connections.");
+
+        // Add the new connection
+        Directory<Connection> connectionDirectory = userContext.getConnectionDirectory();
+        connectionDirectory.add(new APIConnectionWrapper(connection));
+
+        // Return the new connection
+        return connection;
+
+    }
+  
+    /**
+     * Updates an existing connection. If the parent identifier of the
+     * connection is changed, the connection will also be moved to the new
+     * parent group.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection to be updated.
+     *
+     * @param connectionID
+     *     The identifier of the connection to update.
+     *
+     * @param connection
+     *     The connection data to update the specified connection with.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while updating the connection.
+     */
+    @PUT
+    @Path("/{connectionID}")
+    public void updateConnection(@QueryParam("token") String authToken, 
+            @PathParam("dataSource") String authProviderIdentifier,
+            @PathParam("connectionID") String connectionID,
+            APIConnection connection) throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+        
+        // Validate that connection data was provided
+        if (connection == null)
+            throw new GuacamoleClientException("Connection JSON must be submitted when updating connections.");
+
+        // Get the connection directory
+        Directory<Connection> connectionDirectory = userContext.getConnectionDirectory();
+        
+        // Retrieve connection to update
+        Connection existingConnection = retrievalService.retrieveConnection(userContext, connectionID);
+
+        // Build updated configuration
+        GuacamoleConfiguration config = new GuacamoleConfiguration();
+        config.setProtocol(connection.getProtocol());
+        config.setParameters(connection.getParameters());
+
+        // Update the connection
+        existingConnection.setConfiguration(config);
+        existingConnection.setParentIdentifier(connection.getParentIdentifier());
+        existingConnection.setName(connection.getName());
+        existingConnection.setAttributes(connection.getAttributes());
+        connectionDirectory.update(existingConnection);
+
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/connection/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connection/package-info.java b/guacamole/src/main/java/org/apache/guacamole/rest/connection/package-info.java
new file mode 100644
index 0000000..d80155f
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/connection/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes related to the connection manipulation aspect of the Guacamole REST API.
+ */
+package org.apache.guacamole.rest.connection;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroup.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroup.java b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroup.java
new file mode 100644
index 0000000..67dea8e
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroup.java
@@ -0,0 +1,272 @@
+/*
+ * 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.rest.connectiongroup;
+
+import java.util.Collection;
+import java.util.Map;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.apache.guacamole.net.auth.ConnectionGroup;
+import org.apache.guacamole.net.auth.ConnectionGroup.Type;
+import org.apache.guacamole.rest.connection.APIConnection;
+
+/**
+ * A simple connection group to expose through the REST endpoints.
+ * 
+ * @author James Muehlner
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
+public class APIConnectionGroup {
+
+    /**
+     * The identifier of the root connection group.
+     */
+    public static final String ROOT_IDENTIFIER = "ROOT";
+ 
+    /**
+     * The name of this connection group.
+     */
+    private String name;
+    
+    /**
+     * The identifier of this connection group.
+     */
+    private String identifier;
+    
+    /**
+     * The identifier of the parent connection group for this connection group.
+     */
+    private String parentIdentifier;
+    
+    /**
+     * The type of this connection group.
+     */
+    private Type type;
+
+    /**
+     * The count of currently active connections using this connection group.
+     */
+    private int activeConnections;
+
+    /**
+     * All child connection groups. If children are not being queried, this may
+     * be omitted.
+     */
+    private Collection<APIConnectionGroup> childConnectionGroups;
+
+    /**
+     * All child connections. If children are not being queried, this may be
+     * omitted.
+     */
+    private Collection<APIConnection> childConnections;
+    
+    /**
+     * Map of all associated attributes by attribute identifier.
+     */
+    private Map<String, String> attributes;
+
+    /**
+     * Create an empty APIConnectionGroup.
+     */
+    public APIConnectionGroup() {}
+    
+    /**
+     * Create a new APIConnectionGroup from the given ConnectionGroup record.
+     * 
+     * @param connectionGroup The ConnectionGroup record to initialize this 
+     *                        APIConnectionGroup from.
+     */
+    public APIConnectionGroup(ConnectionGroup connectionGroup) {
+
+        // Set connection group information
+        this.identifier = connectionGroup.getIdentifier();
+        this.parentIdentifier = connectionGroup.getParentIdentifier();
+        this.name = connectionGroup.getName();
+        this.type = connectionGroup.getType();
+        this.activeConnections = connectionGroup.getActiveConnections();
+
+        // Associate any attributes
+        this.attributes = connectionGroup.getAttributes();
+
+    }
+
+    /**
+     * Returns the name of this connection group.
+     * @return The name of this connection group.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set the name of this connection group.
+     * @param name The name of this connection group.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the identifier of this connection group.
+     * @return The identifier of this connection group.
+     */
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     * Set the identifier of this connection group.
+     * @param identifier The identifier of this connection group.
+     */
+    public void setIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+    
+    /**
+     * Returns the unique identifier for this connection group.
+     * @return The unique identifier for this connection group.
+     */
+    public String getParentIdentifier() {
+        return parentIdentifier;
+    }
+    /**
+     * Sets the parent connection group identifier for this connection group.
+     * @param parentIdentifier The parent connection group identifier 
+     *                         for this connection group.
+     */
+    public void setParentIdentifier(String parentIdentifier) {
+        this.parentIdentifier = parentIdentifier;
+    }
+
+    /**
+     * Returns the type of this connection group.
+     * @return The type of this connection group.
+     */
+    public Type getType() {
+        return type;
+    }
+
+    /**
+     * Set the type of this connection group.
+     * @param type The Type of this connection group.
+     */
+    public void setType(Type type) {
+        this.type = type;
+    }
+
+    /**
+     * Returns a collection of all child connection groups, or null if children
+     * have not been queried.
+     *
+     * @return
+     *     A collection of all child connection groups, or null if children
+     *     have not been queried.
+     */
+    public Collection<APIConnectionGroup> getChildConnectionGroups() {
+        return childConnectionGroups;
+    }
+
+    /**
+     * Sets the collection of all child connection groups to the given
+     * collection, which may be null if children have not been queried.
+     *
+     * @param childConnectionGroups
+     *     The collection containing all child connection groups of this
+     *     connection group, or null if children have not been queried.
+     */
+    public void setChildConnectionGroups(Collection<APIConnectionGroup> childConnectionGroups) {
+        this.childConnectionGroups = childConnectionGroups;
+    }
+
+    /**
+     * Returns a collection of all child connections, or null if children have
+     * not been queried.
+     *
+     * @return
+     *     A collection of all child connections, or null if children have not
+     *     been queried.
+     */
+    public Collection<APIConnection> getChildConnections() {
+        return childConnections;
+    }
+
+    /**
+     * Sets the collection of all child connections to the given collection,
+     * which may be null if children have not been queried.
+     *
+     * @param childConnections
+     *     The collection containing all child connections of this connection
+     *     group, or null if children have not been queried.
+     */
+    public void setChildConnections(Collection<APIConnection> childConnections) {
+        this.childConnections = childConnections;
+    }
+
+    /**
+     * Returns the number of currently active connections using this
+     * connection group.
+     *
+     * @return
+     *     The number of currently active usages of this connection group.
+     */
+    public int getActiveConnections() {
+        return activeConnections;
+    }
+
+    /**
+     * Set the number of currently active connections using this connection
+     * group.
+     *
+     * @param activeConnections
+     *     The number of currently active usages of this connection group.
+     */
+    public void setActiveUsers(int activeConnections) {
+        this.activeConnections = activeConnections;
+    }
+
+    /**
+     * Returns a map of all attributes associated with this connection group.
+     * Each entry key is the attribute identifier, while each value is the
+     * attribute value itself.
+     *
+     * @return
+     *     The attribute map for this connection group.
+     */
+    public Map<String, String> getAttributes() {
+        return attributes;
+    }
+
+    /**
+     * Sets the map of all attributes associated with this connection group.
+     * Each entry key is the attribute identifier, while each value is the
+     * attribute value itself.
+     *
+     * @param attributes
+     *     The attribute map for this connection group.
+     */
+    public void setAttributes(Map<String, String> attributes) {
+        this.attributes = attributes;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroupWrapper.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroupWrapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroupWrapper.java
new file mode 100644
index 0000000..0c8198a
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroupWrapper.java
@@ -0,0 +1,124 @@
+/*
+ * 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.rest.connectiongroup;
+
+import java.util.Map;
+import java.util.Set;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.net.auth.ConnectionGroup;
+import org.apache.guacamole.protocol.GuacamoleClientInformation;
+
+/**
+ * A wrapper to make an APIConnection look like a ConnectionGroup.
+ * Useful where a org.apache.guacamole.net.auth.ConnectionGroup is required.
+ * 
+ * @author James Muehlner
+ */
+public class APIConnectionGroupWrapper implements ConnectionGroup {
+
+    /**
+     * The wrapped APIConnectionGroup.
+     */
+    private final APIConnectionGroup apiConnectionGroup;
+    
+    /**
+     * Create a new APIConnectionGroupWrapper to wrap the given 
+     * APIConnectionGroup as a ConnectionGroup.
+     * @param apiConnectionGroup the APIConnectionGroup to wrap.
+     */
+    public APIConnectionGroupWrapper(APIConnectionGroup apiConnectionGroup) {
+        this.apiConnectionGroup = apiConnectionGroup;
+    }
+    
+    @Override
+    public String getName() {
+        return apiConnectionGroup.getName();
+    }
+
+    @Override
+    public void setName(String name) {
+        apiConnectionGroup.setName(name);
+    }
+
+    @Override
+    public String getIdentifier() {
+        return apiConnectionGroup.getIdentifier();
+    }
+
+    @Override
+    public void setIdentifier(String identifier) {
+        apiConnectionGroup.setIdentifier(identifier);
+    }
+
+    @Override
+    public String getParentIdentifier() {
+        return apiConnectionGroup.getParentIdentifier();
+    }
+
+    @Override
+    public void setParentIdentifier(String parentIdentifier) {
+        apiConnectionGroup.setParentIdentifier(parentIdentifier);
+    }
+
+    @Override
+    public void setType(Type type) {
+        apiConnectionGroup.setType(type);
+    }
+
+    @Override
+    public Type getType() {
+        return apiConnectionGroup.getType();
+    }
+
+    @Override
+    public int getActiveConnections() {
+        return apiConnectionGroup.getActiveConnections();
+    }
+
+    @Override
+    public Set<String> getConnectionIdentifiers() {
+        throw new UnsupportedOperationException("Operation not supported.");
+    }
+
+    @Override
+    public Set<String> getConnectionGroupIdentifiers() {
+        throw new UnsupportedOperationException("Operation not supported.");
+    }
+
+    @Override
+    public Map<String, String> getAttributes() {
+        return apiConnectionGroup.getAttributes();
+    }
+
+    @Override
+    public void setAttributes(Map<String, String> attributes) {
+        apiConnectionGroup.setAttributes(attributes);
+    }
+
+    @Override
+    public GuacamoleTunnel connect(GuacamoleClientInformation info) throws GuacamoleException {
+        throw new UnsupportedOperationException("Operation not supported.");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupRESTService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupRESTService.java b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupRESTService.java
new file mode 100644
index 0000000..84e5283
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupRESTService.java
@@ -0,0 +1,287 @@
+/*
+ * 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.rest.connectiongroup;
+
+import com.google.inject.Inject;
+import java.util.List;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleException;
+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.net.auth.permission.ObjectPermission;
+import org.apache.guacamole.GuacamoleSession;
+import org.apache.guacamole.rest.ObjectRetrievalService;
+import org.apache.guacamole.rest.auth.AuthenticationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A REST Service for handling connection group CRUD operations.
+ * 
+ * @author James Muehlner
+ */
+@Path("/data/{dataSource}/connectionGroups")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class ConnectionGroupRESTService {
+
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(ConnectionGroupRESTService.class);
+    
+    /**
+     * A service for authenticating users from auth tokens.
+     */
+    @Inject
+    private AuthenticationService authenticationService;
+    
+    /**
+     * Service for convenient retrieval of objects.
+     */
+    @Inject
+    private ObjectRetrievalService retrievalService;
+    
+    /**
+     * Gets an individual connection group.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     * 
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection group to be retrieved.
+     *
+     * @param connectionGroupID
+     *     The ID of the connection group to retrieve.
+     * 
+     * @return
+     *     The connection group, without any descendants.
+     *
+     * @throws GuacamoleException
+     *     If a problem is encountered while retrieving the connection group.
+     */
+    @GET
+    @Path("/{connectionGroupID}")
+    public APIConnectionGroup getConnectionGroup(@QueryParam("token") String authToken,
+            @PathParam("dataSource") String authProviderIdentifier,
+            @PathParam("connectionGroupID") String connectionGroupID)
+            throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+
+        // Retrieve the requested connection group
+        return new APIConnectionGroup(retrievalService.retrieveConnectionGroup(session, authProviderIdentifier, connectionGroupID));
+
+    }
+
+    /**
+     * Gets an individual connection group and all children.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection group to be retrieved.
+     *
+     * @param connectionGroupID
+     *     The ID of the connection group to retrieve.
+     *
+     * @param permissions
+     *     If specified and non-empty, limit the returned list to only those
+     *     connections for which the current user has any of the given
+     *     permissions. Otherwise, all visible connections are returned.
+     *     Connection groups are unaffected by this parameter.
+     * 
+     * @return
+     *     The requested connection group, including all descendants.
+     *
+     * @throws GuacamoleException
+     *     If a problem is encountered while retrieving the connection group or
+     *     its descendants.
+     */
+    @GET
+    @Path("/{connectionGroupID}/tree")
+    public APIConnectionGroup getConnectionGroupTree(@QueryParam("token") String authToken, 
+            @PathParam("dataSource") String authProviderIdentifier,
+            @PathParam("connectionGroupID") String connectionGroupID,
+            @QueryParam("permission") List<ObjectPermission.Type> permissions)
+            throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+
+        // Retrieve the requested tree, filtering by the given permissions
+        ConnectionGroup treeRoot = retrievalService.retrieveConnectionGroup(userContext, connectionGroupID);
+        ConnectionGroupTree tree = new ConnectionGroupTree(userContext, treeRoot, permissions);
+
+        // Return tree as a connection group
+        return tree.getRootAPIConnectionGroup();
+
+    }
+
+    /**
+     * Deletes an individual connection group.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection group to be deleted.
+     *
+     * @param connectionGroupID
+     *     The identifier of the connection group to delete.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while deleting the connection group.
+     */
+    @DELETE
+    @Path("/{connectionGroupID}")
+    public void deleteConnectionGroup(@QueryParam("token") String authToken, 
+            @PathParam("dataSource") String authProviderIdentifier,
+            @PathParam("connectionGroupID") String connectionGroupID)
+            throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+        
+        // Get the connection group directory
+        Directory<ConnectionGroup> connectionGroupDirectory = userContext.getConnectionGroupDirectory();
+
+        // Delete the connection group
+        connectionGroupDirectory.remove(connectionGroupID);
+
+    }
+    
+    /**
+     * Creates a new connection group and returns the new connection group,
+     * with identifier field populated.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext in which the connection group is to be created.
+     *
+     * @param connectionGroup
+     *     The connection group to create.
+     * 
+     * @return
+     *     The new connection group.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while creating the connection group.
+     */
+    @POST
+    public APIConnectionGroup createConnectionGroup(
+            @QueryParam("token") String authToken,
+            @PathParam("dataSource") String authProviderIdentifier,
+            APIConnectionGroup connectionGroup) throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+
+        // Validate that connection group data was provided
+        if (connectionGroup == null)
+            throw new GuacamoleClientException("Connection group JSON must be submitted when creating connections groups.");
+
+        // Add the new connection group
+        Directory<ConnectionGroup> connectionGroupDirectory = userContext.getConnectionGroupDirectory();
+        connectionGroupDirectory.add(new APIConnectionGroupWrapper(connectionGroup));
+
+        // Return the new connection group
+        return connectionGroup;
+
+    }
+    
+    /**
+     * Updates a connection group. If the parent identifier of the
+     * connection group is changed, the connection group will also be moved to
+     * the new parent group.
+     * 
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection group to be updated.
+     *
+     * @param connectionGroupID
+     *     The identifier of the existing connection group to update.
+     *
+     * @param connectionGroup
+     *     The data to update the existing connection group with.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while updating the connection group.
+     */
+    @PUT
+    @Path("/{connectionGroupID}")
+    public void updateConnectionGroup(@QueryParam("token") String authToken, 
+            @PathParam("dataSource") String authProviderIdentifier,
+            @PathParam("connectionGroupID") String connectionGroupID,
+            APIConnectionGroup connectionGroup)
+            throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+        
+        // Validate that connection group data was provided
+        if (connectionGroup == null)
+            throw new GuacamoleClientException("Connection group JSON must be submitted when updating connection groups.");
+
+        // Get the connection group directory
+        Directory<ConnectionGroup> connectionGroupDirectory = userContext.getConnectionGroupDirectory();
+
+        // Retrieve connection group to update
+        ConnectionGroup existingConnectionGroup = retrievalService.retrieveConnectionGroup(userContext, connectionGroupID);
+        
+        // Update the connection group
+        existingConnectionGroup.setName(connectionGroup.getName());
+        existingConnectionGroup.setParentIdentifier(connectionGroup.getParentIdentifier());
+        existingConnectionGroup.setType(connectionGroup.getType());
+        existingConnectionGroup.setAttributes(connectionGroup.getAttributes());
+        connectionGroupDirectory.update(existingConnectionGroup);
+
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupTree.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupTree.java b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupTree.java
new file mode 100644
index 0000000..75e46ae
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/ConnectionGroupTree.java
@@ -0,0 +1,259 @@
+/*
+ * 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.rest.connectiongroup;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.Connection;
+import org.apache.guacamole.net.auth.ConnectionGroup;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.net.auth.permission.ObjectPermission;
+import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
+import org.apache.guacamole.rest.connection.APIConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides access to the entire tree of connection groups and their
+ * connections.
+ *
+ * @author Michael Jumper
+ */
+public class ConnectionGroupTree {
+
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(ConnectionGroupTree.class);
+
+    /**
+     * The context of the user obtaining this tree.
+     */
+    private final UserContext userContext;
+    
+    /**
+     * The root connection group as an APIConnectionGroup.
+     */
+    private final APIConnectionGroup rootAPIGroup;
+
+    /**
+     * All connection groups that have been retrieved, stored by their
+     * identifiers.
+     */
+    private final Map<String, APIConnectionGroup> retrievedGroups =
+            new HashMap<String, APIConnectionGroup>();
+
+    /**
+     * Adds each of the provided connections to the current tree as children
+     * of their respective parents. The parent connection groups must already
+     * be added.
+     *
+     * @param connections
+     *     The connections to add to the tree.
+     * 
+     * @throws GuacamoleException
+     *     If an error occurs while adding the connection to the tree.
+     */
+    private void addConnections(Collection<Connection> connections)
+        throws GuacamoleException {
+
+        // Add each connection to the tree
+        for (Connection connection : connections) {
+
+            // Retrieve the connection's parent group
+            APIConnectionGroup parent = retrievedGroups.get(connection.getParentIdentifier());
+            if (parent != null) {
+
+                Collection<APIConnection> children = parent.getChildConnections();
+                
+                // Create child collection if it does not yet exist
+                if (children == null) {
+                    children = new ArrayList<APIConnection>();
+                    parent.setChildConnections(children);
+                }
+
+                // Add child
+                children.add(new APIConnection(connection));
+                
+            }
+
+            // Warn of internal consistency issues
+            else
+                logger.debug("Connection \"{}\" cannot be added to the tree: parent \"{}\" does not actually exist.",
+                        connection.getIdentifier(),
+                        connection.getParentIdentifier());
+
+        } // end for each connection
+        
+    }
+    
+    /**
+     * Adds each of the provided connection groups to the current tree as
+     * children of their respective parents. The parent connection groups must
+     * already be added.
+     *
+     * @param connectionGroups
+     *     The connection groups to add to the tree.
+     */
+    private void addConnectionGroups(Collection<ConnectionGroup> connectionGroups) {
+
+        // Add each connection group to the tree
+        for (ConnectionGroup connectionGroup : connectionGroups) {
+
+            // Retrieve the connection group's parent group
+            APIConnectionGroup parent = retrievedGroups.get(connectionGroup.getParentIdentifier());
+            if (parent != null) {
+
+                Collection<APIConnectionGroup> children = parent.getChildConnectionGroups();
+                
+                // Create child collection if it does not yet exist
+                if (children == null) {
+                    children = new ArrayList<APIConnectionGroup>();
+                    parent.setChildConnectionGroups(children);
+                }
+
+                // Add child
+                APIConnectionGroup apiConnectionGroup = new APIConnectionGroup(connectionGroup);
+                retrievedGroups.put(connectionGroup.getIdentifier(), apiConnectionGroup);
+                children.add(apiConnectionGroup);
+                
+            }
+
+            // Warn of internal consistency issues
+            else
+                logger.debug("Connection group \"{}\" cannot be added to the tree: parent \"{}\" does not actually exist.",
+                        connectionGroup.getIdentifier(),
+                        connectionGroup.getParentIdentifier());
+
+        } // end for each connection group
+        
+    }
+    
+    /**
+     * Adds all descendants of the given parent groups to their corresponding
+     * parents already stored under root.
+     *
+     * @param parents
+     *     The parents whose descendants should be added to the tree.
+     * 
+     * @param permissions
+     *     If specified and non-empty, limit added connections to only
+     *     connections for which the current user has any of the given
+     *     permissions. Otherwise, all visible connections are added.
+     *     Connection groups are unaffected by this parameter.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while retrieving the descendants.
+     */
+    private void addDescendants(Collection<ConnectionGroup> parents,
+            List<ObjectPermission.Type> permissions)
+        throws GuacamoleException {
+
+        // If no parents, nothing to do
+        if (parents.isEmpty())
+            return;
+
+        Collection<String> childConnectionIdentifiers = new ArrayList<String>();
+        Collection<String> childConnectionGroupIdentifiers = new ArrayList<String>();
+        
+        // Build lists of identifiers for retrieval
+        for (ConnectionGroup parent : parents) {
+            childConnectionIdentifiers.addAll(parent.getConnectionIdentifiers());
+            childConnectionGroupIdentifiers.addAll(parent.getConnectionGroupIdentifiers());
+        }
+
+        // Filter identifiers based on permissions, if requested
+        if (permissions != null && !permissions.isEmpty()) {
+            ObjectPermissionSet permissionSet = userContext.self().getConnectionPermissions();
+            childConnectionIdentifiers = permissionSet.getAccessibleObjects(permissions, childConnectionIdentifiers);
+        }
+        
+        // Retrieve child connections
+        if (!childConnectionIdentifiers.isEmpty()) {
+            Collection<Connection> childConnections = userContext.getConnectionDirectory().getAll(childConnectionIdentifiers);
+            addConnections(childConnections);
+        }
+
+        // Retrieve child connection groups
+        if (!childConnectionGroupIdentifiers.isEmpty()) {
+            Collection<ConnectionGroup> childConnectionGroups = userContext.getConnectionGroupDirectory().getAll(childConnectionGroupIdentifiers);
+            addConnectionGroups(childConnectionGroups);
+            addDescendants(childConnectionGroups, permissions);
+        }
+
+    }
+    
+    /**
+     * Creates a new connection group tree using the given connection group as
+     * the tree root.
+     *
+     * @param userContext
+     *     The context of the user obtaining the connection group tree.
+     *
+     * @param root
+     *     The connection group to use as the root of this connection group
+     *     tree.
+     * 
+     * @param permissions
+     *     If specified and non-empty, limit the contents of the tree to only
+     *     those connections for which the current user has any of the given
+     *     permissions. Otherwise, all visible connections are returned.
+     *     Connection groups are unaffected by this parameter.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while retrieving the tree of connection groups
+     *     and their descendants.
+     */
+    public ConnectionGroupTree(UserContext userContext, ConnectionGroup root,
+            List<ObjectPermission.Type> permissions) throws GuacamoleException {
+
+        this.userContext = userContext;
+        
+        // Store root of tree
+        this.rootAPIGroup = new APIConnectionGroup(root);
+        retrievedGroups.put(root.getIdentifier(), this.rootAPIGroup);
+
+        // Add all descendants
+        addDescendants(Collections.singleton(root), permissions);
+        
+    }
+
+    /**
+     * Returns the entire connection group tree as an APIConnectionGroup. The
+     * returned APIConnectionGroup is the root group and will contain all
+     * descendant connection groups and connections, arranged hierarchically.
+     *
+     * @return
+     *     The root connection group, containing the entire connection group
+     *     tree and all connections.
+     */
+    public APIConnectionGroup getRootAPIConnectionGroup() {
+        return rootAPIGroup;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/package-info.java b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/package-info.java
new file mode 100644
index 0000000..9152989
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes related to the connection group manipulation aspect
+ * of the Guacamole REST API.
+ */
+package org.apache.guacamole.rest.connectiongroup;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecord.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecord.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecord.java
new file mode 100644
index 0000000..e5d92a0
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecord.java
@@ -0,0 +1,163 @@
+/*
+ * 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.rest.history;
+
+import java.util.Date;
+import org.apache.guacamole.net.auth.ConnectionRecord;
+
+/**
+ * A connection record which may be exposed through the REST endpoints.
+ *
+ * @author Michael Jumper
+ */
+public class APIConnectionRecord {
+
+    /**
+     * The identifier of the connection associated with this record.
+     */
+    private final String connectionIdentifier;
+
+    /**
+     * The identifier of the connection associated with this record.
+     */
+    private final String connectionName;
+
+    /**
+     * The date and time the connection began.
+     */
+    private final Date startDate;
+
+    /**
+     * The date and time the connection ended, or null if the connection is
+     * still running or if the end time is unknown.
+     */
+    private final Date endDate;
+
+    /**
+     * The host from which the connection originated, if known.
+     */
+    private final String remoteHost;
+
+    /**
+     * The name of the user who used or is using the connection.
+     */
+    private final String username;
+
+    /**
+     * Whether the connection is currently active.
+     */
+    private final boolean active;
+
+    /**
+     * Creates a new APIConnectionRecord, copying the data from the given
+     * record.
+     *
+     * @param record
+     *     The record to copy data from.
+     */
+    public APIConnectionRecord(ConnectionRecord record) {
+        this.connectionIdentifier = record.getConnectionIdentifier();
+        this.connectionName       = record.getConnectionName();
+        this.startDate            = record.getStartDate();
+        this.endDate              = record.getEndDate();
+        this.remoteHost           = record.getRemoteHost();
+        this.username             = record.getUsername();
+        this.active               = record.isActive();
+    }
+
+    /**
+     * Returns the identifier of the connection associated with this
+     * record.
+     *
+     * @return
+     *     The identifier of the connection associated with this record.
+     */
+    public String getConnectionIdentifier() {
+        return connectionIdentifier;
+    }
+
+    /**
+     * Returns the name of the connection associated with this record.
+     *
+     * @return
+     *     The name of the connection associated with this record.
+     */
+    public String getConnectionName() {
+        return connectionName;
+    }
+
+    /**
+     * Returns the date and time the connection began.
+     *
+     * @return
+     *     The date and time the connection began.
+     */
+    public Date getStartDate() {
+        return startDate;
+    }
+
+    /**
+     * Returns the date and time the connection ended, if applicable.
+     *
+     * @return
+     *     The date and time the connection ended, or null if the connection is
+     *     still running or if the end time is unknown.
+     */
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    /**
+     * Returns the remote host from which this connection originated.
+     *
+     * @return
+     *     The remote host from which this connection originated.
+     */
+    public String getRemoteHost() {
+        return remoteHost;
+    }
+
+    /**
+     * Returns the name of the user who used or is using the connection at the
+     * times given by this connection record.
+     *
+     * @return
+     *     The name of the user who used or is using the associated connection.
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * Returns whether the connection associated with this record is still
+     * active.
+     *
+     * @return
+     *     true if the connection associated with this record is still active,
+     *     false otherwise.
+     */
+    public boolean isActive() {
+        return active;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java
new file mode 100644
index 0000000..7006f58
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/APIConnectionRecordSortPredicate.java
@@ -0,0 +1,148 @@
+/*
+ * 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.rest.history;
+
+import org.apache.guacamole.net.auth.ConnectionRecordSet;
+import org.apache.guacamole.rest.APIError;
+import org.apache.guacamole.rest.APIException;
+
+/**
+ * A sort predicate which species the property to use when sorting connection
+ * records, along with the sort order.
+ *
+ * @author Michael Jumper
+ */
+public class APIConnectionRecordSortPredicate {
+
+    /**
+     * The prefix which will be included before the name of a sortable property
+     * to indicate that the sort order is descending, not ascending.
+     */
+    public static final String DESCENDING_PREFIX = "-";
+
+    /**
+     * All possible property name strings and their corresponding
+     * ConnectionRecordSet.SortableProperty values.
+     */
+    public enum SortableProperty {
+
+        /**
+         * The date that the connection associated with the connection record
+         * began (connected).
+         */
+        startDate(ConnectionRecordSet.SortableProperty.START_DATE);
+
+        /**
+         * The ConnectionRecordSet.SortableProperty that this property name
+         * string represents.
+         */
+        public final ConnectionRecordSet.SortableProperty recordProperty;
+
+        /**
+         * Creates a new SortableProperty which associates the property name
+         * string (identical to its own name) with the given
+         * ConnectionRecordSet.SortableProperty value.
+         *
+         * @param recordProperty
+         *     The ConnectionRecordSet.SortableProperty value to associate with
+         *     the new SortableProperty.
+         */
+        SortableProperty(ConnectionRecordSet.SortableProperty recordProperty) {
+            this.recordProperty = recordProperty;
+        }
+
+    }
+
+    /**
+     * The property to use when sorting ConnectionRecords.
+     */
+    private ConnectionRecordSet.SortableProperty property;
+
+    /**
+     * Whether the requested sort order is descending (true) or ascending
+     * (false).
+     */
+    private boolean descending;
+
+    /**
+     * Parses the given string value, determining the requested sort property
+     * and ordering. Possible values consist of any valid property name, and
+     * may include an optional prefix to denote descending sort order. Each
+     * possible property name is enumerated by the SortableValue enum.
+     *
+     * @param value
+     *     The sort predicate string to parse, which must consist ONLY of a
+     *     valid property name, possibly preceded by the DESCENDING_PREFIX.
+     *
+     * @throws APIException
+     *     If the provided sort predicate string is invalid.
+     */
+    public APIConnectionRecordSortPredicate(String value)
+        throws APIException {
+
+        // Parse whether sort order is descending
+        if (value.startsWith(DESCENDING_PREFIX)) {
+            descending = true;
+            value = value.substring(DESCENDING_PREFIX.length());
+        }
+
+        // Parse sorting property into ConnectionRecordSet.SortableProperty
+        try {
+            this.property = SortableProperty.valueOf(value).recordProperty;
+        }
+
+        // Bail out if sort property is not valid
+        catch (IllegalArgumentException e) {
+            throw new APIException(
+                APIError.Type.BAD_REQUEST,
+                String.format("Invalid sort property: \"%s\"", value)
+            );
+        }
+
+    }
+
+    /**
+     * Returns the SortableProperty defined by ConnectionRecordSet which
+     * represents the property requested.
+     *
+     * @return
+     *     The ConnectionRecordSet.SortableProperty which refers to the same
+     *     property as the string originally provided when this
+     *     APIConnectionRecordSortPredicate was created.
+     */
+    public ConnectionRecordSet.SortableProperty getProperty() {
+        return property;
+    }
+
+    /**
+     * Returns whether the requested sort order is descending.
+     *
+     * @return
+     *     true if the sort order is descending, false if the sort order is
+     *     ascending.
+     */
+    public boolean isDescending() {
+        return descending;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryRESTService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryRESTService.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryRESTService.java
new file mode 100644
index 0000000..f5d4651
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/HistoryRESTService.java
@@ -0,0 +1,147 @@
+/*
+ * 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.rest.history;
+
+import com.google.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.ConnectionRecord;
+import org.apache.guacamole.net.auth.ConnectionRecordSet;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.GuacamoleSession;
+import org.apache.guacamole.rest.ObjectRetrievalService;
+import org.apache.guacamole.rest.auth.AuthenticationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A REST Service for retrieving and managing the history records of Guacamole
+ * objects.
+ *
+ * @author Michael Jumper
+ */
+@Path("/data/{dataSource}/history")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class HistoryRESTService {
+
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(HistoryRESTService.class);
+
+    /**
+     * The maximum number of history records to return in any one response.
+     */
+    private static final int MAXIMUM_HISTORY_SIZE = 1000;
+
+    /**
+     * A service for authenticating users from auth tokens.
+     */
+    @Inject
+    private AuthenticationService authenticationService;
+
+    /**
+     * Service for convenient retrieval of objects.
+     */
+    @Inject
+    private ObjectRetrievalService retrievalService;
+
+    /**
+     * Retrieves the usage history for all connections, restricted by optional
+     * filter parameters.
+     *
+     * @param authToken
+     *     The authentication token that is used to authenticate the user
+     *     performing the operation.
+     *
+     * @param authProviderIdentifier
+     *     The unique identifier of the AuthenticationProvider associated with
+     *     the UserContext containing the connection whose history is to be
+     *     retrieved.
+     *
+     * @param requiredContents
+     *     The set of strings that each must occur somewhere within the
+     *     returned connection records, whether within the associated username,
+     *     the name of the associated connection, or any associated date. If
+     *     non-empty, any connection record not matching each of the strings
+     *     within the collection will be excluded from the results.
+     *
+     * @param sortPredicates
+     *     A list of predicates to apply while sorting the resulting connection
+     *     records, describing the properties involved and the sort order for
+     *     those properties.
+     *
+     * @return
+     *     A list of connection records, describing the start and end times of
+     *     various usages of this connection.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while retrieving the connection history.
+     */
+    @GET
+    @Path("/connections")
+    public List<APIConnectionRecord> getConnectionHistory(@QueryParam("token") String authToken,
+            @PathParam("dataSource") String authProviderIdentifier,
+            @QueryParam("contains") List<String> requiredContents,
+            @QueryParam("order") List<APIConnectionRecordSortPredicate> sortPredicates)
+            throws GuacamoleException {
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+
+        // Retrieve overall connection history
+        ConnectionRecordSet history = userContext.getConnectionHistory();
+
+        // Restrict to records which contain the specified strings
+        for (String required : requiredContents) {
+            if (!required.isEmpty())
+                history = history.contains(required);
+        }
+
+        // Sort according to specified ordering
+        for (APIConnectionRecordSortPredicate predicate : sortPredicates)
+            history = history.sort(predicate.getProperty(), predicate.isDescending());
+
+        // Limit to maximum result size
+        history = history.limit(MAXIMUM_HISTORY_SIZE);
+
+        // Convert record set to collection of API connection records
+        List<APIConnectionRecord> apiRecords = new ArrayList<APIConnectionRecord>();
+        for (ConnectionRecord record : history.asCollection())
+            apiRecords.add(new APIConnectionRecord(record));
+
+        // Return the converted history
+        return apiRecords;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/history/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/history/package-info.java b/guacamole/src/main/java/org/apache/guacamole/rest/history/package-info.java
new file mode 100644
index 0000000..ed2d0fc
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/history/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes related to retrieval or maintenance of history records using the
+ * Guacamole REST API.
+ */
+package org.apache.guacamole.rest.history;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/language/LanguageRESTService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/language/LanguageRESTService.java b/guacamole/src/main/java/org/apache/guacamole/rest/language/LanguageRESTService.java
new file mode 100644
index 0000000..2595ae3
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/language/LanguageRESTService.java
@@ -0,0 +1,63 @@
+/*
+ * 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.rest.language;
+
+import com.google.inject.Inject;
+import java.util.Map;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import org.apache.guacamole.extension.LanguageResourceService;
+
+
+/**
+ * A REST Service for handling the listing of languages.
+ * 
+ * @author James Muehlner
+ */
+@Path("/languages")
+@Produces(MediaType.APPLICATION_JSON)
+public class LanguageRESTService {
+
+    /**
+     * Service for retrieving information regarding available language
+     * resources.
+     */
+    @Inject
+    private LanguageResourceService languageResourceService;
+
+    /**
+     * Returns a map of all available language keys to their corresponding
+     * human-readable names.
+     * 
+     * @return
+     *     A map of languages defined in the system, of language key to 
+     *     display name.
+     */
+    @GET
+    public Map<String, String> getLanguages() {
+        return languageResourceService.getLanguageNames();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/language/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/language/package-info.java b/guacamole/src/main/java/org/apache/guacamole/rest/language/package-info.java
new file mode 100644
index 0000000..cb0a190
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/language/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes related to the language retrieval aspect of the Guacamole REST API.
+ */
+package org.apache.guacamole.rest.language;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/package-info.java b/guacamole/src/main/java/org/apache/guacamole/rest/package-info.java
new file mode 100644
index 0000000..541ff76
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes related to the basic Guacamole REST API.
+ */
+package org.apache.guacamole.rest;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/patch/PatchRESTService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/patch/PatchRESTService.java b/guacamole/src/main/java/org/apache/guacamole/rest/patch/PatchRESTService.java
new file mode 100644
index 0000000..1d823b1
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/patch/PatchRESTService.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2016 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.rest.patch;
+
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
+import org.apache.guacamole.extension.PatchResourceService;
+import org.apache.guacamole.resource.Resource;
+
+/**
+ * A REST Service for handling the listing of HTML patches.
+ *
+ * @author Michael Jumper
+ */
+@Path("/patches")
+@Produces(MediaType.APPLICATION_JSON)
+public class PatchRESTService {
+
+    /**
+     * Service for retrieving information regarding available HTML patch
+     * resources.
+     */
+    @Inject
+    private PatchResourceService patchResourceService;
+
+    /**
+     * Reads the entire contents of the given resource as a String. The
+     * resource is assumed to be encoded in UTF-8.
+     *
+     * @param resource
+     *     The resource to read as a new String.
+     *
+     * @return
+     *     A new String containing the contents of the given resource.
+     *
+     * @throws IOException
+     *     If an I/O error prevents reading the resource.
+     */
+    private String readResourceAsString(Resource resource) throws IOException {
+
+        StringBuilder contents = new StringBuilder();
+
+        // Read entire resource into StringBuilder one chunk at a time
+        Reader reader = new InputStreamReader(resource.asStream(), "UTF-8");
+        try {
+
+            char buffer[] = new char[8192];
+            int length;
+
+            while ((length = reader.read(buffer)) != -1) {
+                contents.append(buffer, 0, length);
+            }
+
+        }
+
+        // Ensure resource is always closed
+        finally {
+            reader.close();
+        }
+
+        return contents.toString();
+
+    }
+
+    /**
+     * Returns a list of all available HTML patches, in the order they should
+     * be applied. Each patch is raw HTML containing additional meta tags
+     * describing how and where the patch should be applied.
+     *
+     * @return
+     *     A list of all HTML patches defined in the system, in the order they
+     *     should be applied.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs preventing any HTML patch from being read.
+     */
+    @GET
+    public List<String> getPatches() throws GuacamoleException {
+
+        try {
+
+            // Allocate a list of equal size to the total number of patches
+            List<Resource> resources = patchResourceService.getPatchResources();
+            List<String> patches = new ArrayList<String>(resources.size());
+
+            // Convert each patch resource to a string
+            for (Resource resource : resources) {
+                patches.add(readResourceAsString(resource));
+            }
+
+            // Return all patches in string form
+            return patches;
+
+        }
+
+        // Bail out entirely on error
+        catch (IOException e) {
+            throw new GuacamoleServerException("Unable to read HTML patches.", e);
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/rest/patch/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/patch/package-info.java b/guacamole/src/main/java/org/apache/guacamole/rest/patch/package-info.java
new file mode 100644
index 0000000..399c329
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/patch/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+/**
+ * Classes related to the HTML patch retrieval aspect of the Guacamole REST API.
+ */
+package org.apache.guacamole.rest.patch;
+