From 7235ed980fd78b329d73deeebb8d82678bafe9b5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 31 Aug 2015 14:08:11 -0700 Subject: [PATCH] GUAC-586: Invoke REST service functions across multiple data sources using dataSourceService.apply(). --- .../controllers/manageUserController.js | 60 +-------- .../navigation/services/userPageService.js | 11 +- .../app/rest/services/dataSourceService.js | 125 ++++++++++++++++++ .../app/rest/services/permissionService.js | 57 -------- .../webapp/app/rest/services/userService.js | 59 --------- .../settings/directives/guacSettingsUsers.js | 5 +- 6 files changed, 138 insertions(+), 179 deletions(-) create mode 100644 guacamole/src/main/webapp/app/rest/services/dataSourceService.js diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index c302f0669..5e8eaf1a3 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -39,6 +39,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto var $q = $injector.get('$q'); var authenticationService = $injector.get('authenticationService'); var connectionGroupService = $injector.get('connectionGroupService'); + var dataSourceService = $injector.get('dataSourceService'); var guacNotification = $injector.get('guacNotification'); var permissionService = $injector.get('permissionService'); var schemaService = $injector.get('schemaService'); @@ -326,63 +327,8 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto $scope.attributes = attributes; }); - /** - * Retrieves all user objects having the given username from each of the - * given data sources, returning a promise which resolves to a map of those - * users by data source identifier. If any data source returns an error, - * that data source is omitted from the results. - * - * @param {String[]} dataSources - * The identifiers of the data sources to query. - * - * @param {String} username - * The username of the user to retrieve from each of the given data - * sources. - * - * @returns {Promise.>} - * A promise which resolves to a map of user objects by data source - * identifier. - */ - var getUserAccounts = function getUserAccounts(dataSources, username) { - - var deferred = $q.defer(); - - var userRequests = []; - var accounts = {}; - - // Retrieve the requested user account from all data sources - angular.forEach(dataSources, function retrieveUser(dataSource) { - - // Add promise to list of pending requests - var deferredUserRequest = $q.defer(); - userRequests.push(deferredUserRequest.promise); - - // Retrieve user from data source - userService.getUser(dataSource, username) - .success(function userRetrieved(user) { - accounts[dataSource] = user; - deferredUserRequest.resolve(); - }) - - // Ignore any errors - .error(function userRetrievalFailed() { - deferredUserRequest.resolve(); - }); - - }); - - // Resolve when all requests are completed - $q.all(userRequests) - .then(function accountsRetrieved() { - deferred.resolve(accounts); - }); - - return deferred.promise; - - }; - // Pull user data - getUserAccounts(dataSources, username) + dataSourceService.apply(userService.getUser, dataSources, username) .then(function usersReceived(users) { // Get user for currently-selected data source @@ -427,7 +373,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }); // Retrieve all connections for which we have ADMINISTER permission - connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER]) + connectionGroupService.getConnectionGroupTree(dataSource, ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER]) .success(function connectionGroupReceived(rootGroup) { $scope.rootGroup = rootGroup; }); diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index daefb4707..574307104 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -34,8 +34,9 @@ angular.module('navigation').factory('userPageService', ['$injector', // Get required services var $q = $injector.get('$q'); var authenticationService = $injector.get('authenticationService'); - var connectionGroupService = $injector.get("connectionGroupService"); - var permissionService = $injector.get("permissionService"); + var connectionGroupService = $injector.get('connectionGroupService'); + var dataSourceService = $injector.get('dataSourceService'); + var permissionService = $injector.get('permissionService'); var service = {}; @@ -259,7 +260,8 @@ angular.module('navigation').factory('userPageService', ['$injector', var deferred = $q.defer(); // Retrieve current permissions - permissionService.getAllPermissions( + dataSourceService.apply( + permissionService.getPermissions, authenticationService.getAvailableDataSources(), authenticationService.getCurrentUsername() ) @@ -346,7 +348,8 @@ angular.module('navigation').factory('userPageService', ['$injector', }); // Retrieve current permissions - permissionService.getAllPermissions( + dataSourceService.apply( + permissionService.getPermissions, authenticationService.getAvailableDataSources(), authenticationService.getCurrentUsername() ) diff --git a/guacamole/src/main/webapp/app/rest/services/dataSourceService.js b/guacamole/src/main/webapp/app/rest/services/dataSourceService.js new file mode 100644 index 000000000..70e7ad48d --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/services/dataSourceService.js @@ -0,0 +1,125 @@ +/* + * 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. + */ + +/** + * Service which contains all REST API response caches. + */ +angular.module('rest').factory('dataSourceService', ['$injector', + function dataSourceService($injector) { + + // Required services + var $q = $injector.get('$q'); + + // Service containing all caches + var service = {}; + + /** + * Invokes the given function once for each of the given data sources, + * passing that data source as the first argument to each invocation, + * followed by any additional arguments passed to apply(). The results of + * each invocation are aggregated into a map by data source identifier, + * and handled through a single promise which is resolved or rejected + * depending on the success/failure of each resulting REST call. Any error + * results in rejection of the entire apply() operation, except 404 ("NOT + * FOUND") errors, which are ignored. + * + * @param {Function} fn + * The function to call for each of the given data sources. The data + * source identifier will be given as the first argument, followed by + * the rest of the arguments given to apply(), in order. The function + * must return a Promise which is resolved or rejected depending on the + * result of the REST call. + * + * @param {String[]} dataSources + * The array or data source identifiers against which the given + * function should be called. + * + * @param {...*} args + * Any additional arguments to pass to the given function each time it + * is called. + * + * @returns {Promise.>} + * A Promise which resolves with a map of data source identifier to + * corresponding result. The result will be the exact object or value + * provided as the resolution to the Promise returned by calls to the + * given function. + */ + service.apply = function apply(fn, dataSources) { + + var deferred = $q.defer(); + + var requests = []; + var results = {}; + + // Build array of arguments to pass to the given function + var args = []; + for (var i = 2; i < arguments.length; i++) + args.push(arguments[i]); + + // Retrieve the root group from all data sources + angular.forEach(dataSources, function invokeAgainstDataSource(dataSource) { + + // Add promise to list of pending requests + var deferredRequest = $q.defer(); + requests.push(deferredRequest.promise); + + // Retrieve root group from data source + fn.apply(this, [dataSource].concat(args)) + + // Store result on success + .then(function immediateRequestSucceeded(response) { + results[dataSource] = response.data; + deferredRequest.resolve(); + }, + + // Fail on any errors (except "NOT FOUND") + function immediateRequestFailed(response) { + + // Ignore "NOT FOUND" errors + if (response.status === 404) + deferredRequest.resolve(); + + // Explicitly abort for all other errors + else + deferredRequest.reject(response); + + }); + + }); + + // Resolve if all requests succeed + $q.all(requests).then(function requestsSucceeded() { + deferred.resolve(results); + }, + + // Reject if at least one request fails + function requestFailed(response) { + deferred.reject(response); + }); + + return deferred.promise; + + }; + + return service; + +}]); diff --git a/guacamole/src/main/webapp/app/rest/services/permissionService.js b/guacamole/src/main/webapp/app/rest/services/permissionService.js index ca9a2ef89..3cef2b9bc 100644 --- a/guacamole/src/main/webapp/app/rest/services/permissionService.js +++ b/guacamole/src/main/webapp/app/rest/services/permissionService.js @@ -71,63 +71,6 @@ angular.module('rest').factory('permissionService', ['$injector', }; - /** - * Returns a promise which resolves with all permissions available to the - * given user, as a map of all PermissionSet objects by the identifier of - * their corresponding data source. All given data sources are queried. If - * an error occurs while retrieving any PermissionSet, the promise will be - * rejected. - * - * @param {String[]} dataSources - * The unique identifier of the data sources containing the user whose - * permissions should be retrieved. These identifiers corresponds to - * AuthenticationProviders within the Guacamole web application. - * - * @param {String} username - * The username of the user to retrieve the permissions for. - * - * @returns {Promise.>} - * A promise which resolves with all permissions available to the - * current user, as a map of app PermissionSet objects by the - * identifier of their corresponding data source. - */ - service.getAllPermissions = function getAllPermissions(dataSources, username) { - - var deferred = $q.defer(); - - var permissionSetRequests = []; - var permissionSets = {}; - - // Retrieve all permissions from all data sources - angular.forEach(dataSources, function retrievePermissions(dataSource) { - permissionSetRequests.push( - service.getPermissions(dataSource, username) - .success(function permissionsRetrieved(permissions) { - permissionSets[dataSource] = permissions; - }) - ); - }); - - // Resolve when all requests are completed - $q.all(permissionSetRequests) - .then( - - // All requests completed successfully - function allPermissionsRetrieved() { - deferred.resolve(permissionSets); - }, - - // At least one request failed - function permissionRetrievalFailed(e) { - deferred.reject(e); - } - - ); - - return deferred.promise; - - }; - /** * Makes a request to the REST API to add permissions for a given user, * returning a promise that can be used for processing the results of the diff --git a/guacamole/src/main/webapp/app/rest/services/userService.js b/guacamole/src/main/webapp/app/rest/services/userService.js index 268e00e69..e7b3a359e 100644 --- a/guacamole/src/main/webapp/app/rest/services/userService.js +++ b/guacamole/src/main/webapp/app/rest/services/userService.js @@ -78,65 +78,6 @@ angular.module('rest').factory('userService', ['$injector', }; - /** - * Returns a promise which resolves with all users accessible by the - * current user, as a map of @link{User} arrays by the identifier of their - * corresponding data source. All given data sources are queried. If an - * error occurs while retrieving any user, the promise will be rejected. - * - * @param {String[]} dataSources - * The unique identifier of the data sources containing the user to be - * retrieved. These identifiers correspond to AuthenticationProviders - * within the Guacamole web application. - * - * @param {String[]} [permissionTypes] - * The set of permissions to filter with. A user must have one or more - * of these permissions for a user to appear in the result. - * If null, no filtering will be performed. Valid values are listed - * within PermissionSet.ObjectType. - * - * @returns {Promise.>} - * A promise which resolves with all user objects available to the - * current user, as a map of @link{User} arrays grouped by the - * identifier of their corresponding data source. - */ - service.getAllUsers = function getAllUsers(dataSources, permissionTypes) { - - var deferred = $q.defer(); - - var userRequests = []; - var userArrays = {}; - - // Retrieve all users from all data sources - angular.forEach(dataSources, function retrieveUsers(dataSource) { - userRequests.push( - service.getUsers(dataSource, permissionTypes) - .success(function usersRetrieved(users) { - userArrays[dataSource] = users; - }) - ); - }); - - // Resolve when all requests are completed - $q.all(userRequests) - .then( - - // All requests completed successfully - function allUsersRetrieved() { - deferred.resolve(userArrays); - }, - - // At least one request failed - function userRetrievalFailed(e) { - deferred.reject(e); - } - - ); - - return deferred.promise; - - }; - /** * Makes a request to the REST API to get the user having the given * username, returning a promise that provides the corresponding diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js index 494148888..e6f6dd709 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js @@ -43,6 +43,7 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings // Required services var $location = $injector.get('$location'); var authenticationService = $injector.get('authenticationService'); + var dataSourceService = $injector.get('dataSourceService'); var guacNotification = $injector.get('guacNotification'); var permissionService = $injector.get('permissionService'); var userService = $injector.get('userService'); @@ -193,7 +194,7 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings }; // Retrieve current permissions - permissionService.getAllPermissions(dataSources, currentUsername) + dataSourceService.apply(permissionService.getPermissions, dataSources, currentUsername) .then(function permissionsRetrieved(permissions) { // Store retrieved permissions @@ -206,7 +207,7 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings }); // Retrieve all users for whom we have UPDATE or DELETE permission - userService.getAllUsers(dataSources, [ + dataSourceService.apply(userService.getUsers, dataSources, [ PermissionSet.ObjectPermissionType.UPDATE, PermissionSet.ObjectPermissionType.DELETE ])