GUAC-932: Add support for recursive query (and break JS).

This commit is contained in:
Michael Jumper
2014-12-16 17:47:15 -08:00
parent 4d255e21a1
commit ac6e92860b
6 changed files with 227 additions and 134 deletions

View File

@@ -24,7 +24,6 @@ package org.glyptodon.guacamole.net.basic.rest;
import com.google.inject.AbstractModule;
import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionService;
import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupService;
import org.glyptodon.guacamole.net.basic.rest.protocol.ProtocolRetrievalService;
/**
@@ -40,7 +39,6 @@ public class RESTModule extends AbstractModule {
// Bind generic low-level services
bind(ConnectionService.class);
bind(ConnectionGroupService.class);
bind(ProtocolRetrievalService.class);
}

View File

@@ -22,9 +22,11 @@
package org.glyptodon.guacamole.net.basic.rest.connectiongroup;
import java.util.Collection;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
import org.glyptodon.guacamole.net.auth.ConnectionGroup.Type;
import org.glyptodon.guacamole.net.basic.rest.connection.APIConnection;
/**
* A simple connection group to expose through the REST endpoints.
@@ -58,6 +60,18 @@ public class APIConnectionGroup {
* The type of this connection group.
*/
private Type type;
/**
* 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;
/**
* Create an empty APIConnectionGroup.
@@ -148,4 +162,52 @@ public class APIConnectionGroup {
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;
}
}

View File

@@ -23,7 +23,8 @@
package org.glyptodon.guacamole.net.basic.rest.connectiongroup;
import com.google.inject.Inject;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
@@ -37,12 +38,15 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status;
import org.glyptodon.guacamole.GuacamoleClientException;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
import org.glyptodon.guacamole.net.auth.Connection;
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
import org.glyptodon.guacamole.net.basic.rest.HTTPException;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.glyptodon.guacamole.net.basic.rest.connection.APIConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,55 +72,101 @@ public class ConnectionGroupRESTService {
private AuthenticationService authenticationService;
/**
* A service for managing the REST endpoint APIConnection objects.
* Retrieves the given connection group from the user context, including
* all descendant connections and groups if requested.
*
* @param userContext
* The user context from which to retrieve the connection group.
*
* @param identifier
* The unique identifier of the connection group to retrieve.
*
* @param includeDescendants
* Whether the descendant connections and groups of the given
* connection group should also be retrieved.
*
* @return
* The requested connection group, or null if no such connection group
* exists.
*
* @throws GuacamoleException
* If an error occurs while retrieving the requested connection group
* or any of its descendants.
*/
@Inject
private ConnectionGroupService connectionGroupService;
/**
* The ID that will be guaranteed to refer to the root connection group.
*/
private static final String ROOT_CONNECTION_GROUP_ID = "ROOT";
/**
* Gets a list of connection groups with the given ConnectionGroup parentID.
* If no parentID is provided, returns the connection groups from the root group.
*
* @param authToken The authentication token that is used to authenticate
* the user performing the operation.
* @param parentID The ID of the ConnectionGroup the connection groups
* belong to. If null, the root connection group will be used.
* @return The connection list.
* @throws GuacamoleException If a problem is encountered while listing connection groups.
*/
@GET
@AuthProviderRESTExposure
public List<APIConnectionGroup> getConnectionGroups(@QueryParam("token") String authToken, @QueryParam("parentID") String parentID)
private APIConnectionGroup retrieveConnectionGroup(UserContext userContext,
String identifier, boolean includeDescendants)
throws GuacamoleException {
UserContext userContext = authenticationService.getUserContext(authToken);
ConnectionGroup rootGroup = userContext.getRootConnectionGroup();
// If the parent connection group is passed in, try to find it.
ConnectionGroup parentConnectionGroup;
if (parentID == null)
parentConnectionGroup = userContext.getRootConnectionGroup();
ConnectionGroup connectionGroup;
// Use root group if requested
if (identifier == null || identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER))
connectionGroup = rootGroup;
// Otherwise, query requested group using root group directory
else {
ConnectionGroup rootGroup = userContext.getRootConnectionGroup();
Directory<String, ConnectionGroup> connectionGroupDirectory = rootGroup.getConnectionGroupDirectory();
parentConnectionGroup = connectionGroupDirectory.get(parentID);
Directory<String, ConnectionGroup> connectionGroupDirectory =
rootGroup.getConnectionGroupDirectory();
// Get the connection group from root directory
connectionGroup = connectionGroupDirectory.get(identifier);
if (connectionGroup == null)
return null;
}
if (parentConnectionGroup == null)
throw new HTTPException(Status.NOT_FOUND, "No connection group found with the provided parentID.");
// Wrap queried connection group
APIConnectionGroup apiConnectionGroup = new APIConnectionGroup(connectionGroup);
Directory<String, ConnectionGroup> connectionGroupDirectory =
parentConnectionGroup.getConnectionGroupDirectory();
// Recursively query all descendants if necessary
if (includeDescendants) {
// Query all child connections
Collection<APIConnection> apiConnections = new ArrayList<APIConnection>();
Directory<String, Connection> connectionDirectory = connectionGroup.getConnectionDirectory();
for (String childIdentifier : connectionDirectory.getIdentifiers()) {
// Pull current connection - silently ignore if connection was removed prior to read
Connection childConnection = connectionDirectory.get(childIdentifier);
if (childConnection == null)
continue;
apiConnections.add(new APIConnection(childConnection));
}
// Associate child connections with current connection group
apiConnectionGroup.setChildConnections(apiConnections);
// Query all child connection groups
Collection<APIConnectionGroup> apiConnectionGroups = new ArrayList<APIConnectionGroup>();
Directory<String, ConnectionGroup> groupDirectory = connectionGroup.getConnectionGroupDirectory();
for (String childIdentifier : groupDirectory.getIdentifiers()) {
// Pull current connection group - silently ignore if connection group was removed prior to read
APIConnectionGroup childConnectionGroup = retrieveConnectionGroup(userContext, childIdentifier, true);
if (childConnectionGroup == null)
continue;
apiConnectionGroups.add(childConnectionGroup);
}
// Associate child groups with current connection group
apiConnectionGroup.setChildConnectionGroups(apiConnectionGroups);
}
// Return the connectiion group
return apiConnectionGroup;
// Return the converted connection group list
return connectionGroupService.convertConnectionGroupList(connectionGroupDirectory);
}
/**
* Gets an individual connection group.
*
@@ -133,27 +183,50 @@ public class ConnectionGroupRESTService {
@PathParam("connectionGroupID") String connectionGroupID) throws GuacamoleException {
UserContext userContext = authenticationService.getUserContext(authToken);
// Get the connection group directory
ConnectionGroup rootGroup = userContext.getRootConnectionGroup();
// Return the root group if it was asked for
if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID))
return new APIConnectionGroup(rootGroup);
Directory<String, ConnectionGroup> connectionGroupDirectory =
rootGroup.getConnectionGroupDirectory();
// Get the connection group
ConnectionGroup connectionGroup = connectionGroupDirectory.get(connectionGroupID);
// Retrieve requested connection group only
APIConnectionGroup connectionGroup = retrieveConnectionGroup(userContext, connectionGroupID, false);
if (connectionGroup == null)
throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided ID.");
throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\"");
// Return the connectiion group
return new APIConnectionGroup(connectionGroup);
return connectionGroup;
}
/**
* Gets an individual connection group and all children.
*
* @param authToken
* The authentication token that is used to authenticate the user
* performing the operation.
*
* @param connectionGroupID
* The ID of the ConnectionGroup.
*
* @return
* The connection group.
*
* @throws GuacamoleException
* If a problem is encountered while retrieving the connection group or
* its descendants.
*/
@GET
@Path("/{connectionGroupID}/tree")
@AuthProviderRESTExposure
public APIConnectionGroup getConnectionGroupTree(@QueryParam("token") String authToken,
@PathParam("connectionGroupID") String connectionGroupID) throws GuacamoleException {
UserContext userContext = authenticationService.getUserContext(authToken);
// Retrieve requested connection group and all descendants
APIConnectionGroup connectionGroup = retrieveConnectionGroup(userContext, connectionGroupID, true);
if (connectionGroup == null)
throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\"");
return connectionGroup;
}
/**
* Deletes an individual connection group.
*
@@ -174,7 +247,7 @@ public class ConnectionGroupRESTService {
ConnectionGroup rootGroup = userContext.getRootConnectionGroup();
// Use the root group if it was asked for
if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID))
if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER))
connectionGroupID = rootGroup.getIdentifier();
Directory<String, ConnectionGroup> connectionGroupDirectory =
@@ -263,7 +336,7 @@ public class ConnectionGroupRESTService {
ConnectionGroup rootGroup = userContext.getRootConnectionGroup();
// Use the root group if it was asked for
if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID))
if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER))
connectionGroupID = rootGroup.getIdentifier();
Directory<String, ConnectionGroup> connectionGroupDirectory =
@@ -300,7 +373,7 @@ public class ConnectionGroupRESTService {
ConnectionGroup rootGroup = userContext.getRootConnectionGroup();
// Use the root group if it was asked for
if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID))
if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER))
connectionGroupID = rootGroup.getIdentifier();
Directory<String, ConnectionGroup> connectionGroupDirectory =

View File

@@ -1,59 +0,0 @@
/*
* 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.glyptodon.guacamole.net.basic.rest.connectiongroup;
import java.util.ArrayList;
import java.util.List;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
import org.glyptodon.guacamole.net.auth.Directory;
/**
* A service for performing useful manipulations on REST ConnectionGroups.
*
* @author James Muehlner
*/
public class ConnectionGroupService {
/**
* Converts a ConnectionGroup directory to a list of APIConnectionGroup
* objects for exposing with the REST endpoints.
*
* @param connectionGroupDirectory The ConnectionGroup Directory to convert for REST endpoint use.
* @return A List of APIConnectionGroup objects for use with the REST endpoint.
* @throws GuacamoleException If an error occurs while converting the
* connection group directory.
*/
public List<APIConnectionGroup> convertConnectionGroupList(
Directory<String, ConnectionGroup> connectionGroupDirectory) throws GuacamoleException {
List<APIConnectionGroup> restConnectionGroups = new ArrayList<APIConnectionGroup>();
for (String connectionGroupID : connectionGroupDirectory.getIdentifiers())
restConnectionGroups.add(new APIConnectionGroup(connectionGroupDirectory.get(connectionGroupID)));
return restConnectionGroups;
}
}

View File

@@ -29,26 +29,26 @@ angular.module('rest').factory('connectionGroupService', ['$http', 'authenticati
var service = {};
/**
* Makes a request to the REST API to get the list of connection groups,
* returning a promise that provides an array of
* @link{ConnectionGroup} objects if successful.
* Makes a request to the REST API to get an individual connection group
* and all descendants, returning a promise that provides the corresponding
* @link{ConnectionGroup} if successful. Descendant groups and connections
* will be stored as children of that connection group.
*
* @param {String} [parentID=ConnectionGroup.ROOT_IDENTIFIER]
* The ID of the connection group whose child connection groups should
* be returned. If not provided, the root connection group will be
* used by default.
* @param {String} [connectionGroupID=ConnectionGroup.ROOT_IDENTIFIER]
* The ID of the connection group to retrieve. If not provided, the
* root connection group will be retrieved by default.
*
* @returns {Promise.<ConnectionGroup[]>}
* A promise which will resolve with an array of @link{ConnectionGroup}
* objects upon success.
* @returns {Promise.ConnectionGroup}
* A promise which will resolve with a @link{ConnectionGroup} upon
* success.
*/
service.getConnectionGroups = function getConnectionGroups(parentID) {
service.getConnectionGroupTree = function getConnectionGroupTree(connectionGroupID) {
var parentIDParam = "";
if (parentID)
parentIDParam = "&parentID=" + parentID;
// Use the root connection group ID if no ID is passed in
connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER;
return $http.get("api/connectionGroup?token=" + authenticationService.getCurrentToken() + parentIDParam);
return $http.get("api/connectionGroup/" + connectionGroupID + "/tree?token=" + authenticationService.getCurrentToken());
};
/**
@@ -70,6 +70,7 @@ angular.module('rest').factory('connectionGroupService', ['$http', 'authenticati
connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER;
return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + authenticationService.getCurrentToken());
};
/**

View File

@@ -73,6 +73,24 @@ angular.module('rest').factory('ConnectionGroup', [function defineConnectionGrou
*/
this.type = template.type || ConnectionGroup.Type.ORGANIZATIONAL;
/**
* An array of all child connections, if known. This property may be
* null or undefined if children have not been queried, and thus the
* child connections are unknown.
*
* @type Connection[]
*/
this.childConnections = template.childConnections;
/**
* An array of all child connection groups, if known. This property may
* be null or undefined if children have not been queried, and thus the
* child connection groups are unknown.
*
* @type ConnectionGroup[]
*/
this.childConnectionGroups = template.childConnectionGroups;
};
/**