# Source: platform/Platform.js

    (function(window)
    {
        var Gitana = window.Gitana;

        Gitana.Platform = Gitana.ContainedDataStore.extend(
        /** @lends Gitana.Platform.prototype */
        {
            /**
             * @constructs
             * @augments Gitana.DataStore
             *
             * @class Platform
             *
             * @param {Gitana.Cluster} cluster
             * @param [Object] object json object (if no callback required for populating)
             */
            constructor: function(cluster, object)
            {
                this.objectType = function() { return "Gitana.Platform"; };

                this.base(cluster, object);

                //////////////////////////////////////////////////////////////////////////////////////////////
                //
                // PRIVILEGED METHODS
                //
                //////////////////////////////////////////////////////////////////////////////////////////////

                this.getCluster = function()
                {
                    return cluster;
                };

                this.getClusterId = function()
                {
                    return cluster.getId();
                };

            },

            /**
             * This method is provided to make the platform datastore compatible for teams.
             */
            getPlatform: function()
            {
                return this;
            },

            /**
             * Reads the cluster.
             *
             * @chained cluster
             */
            readCluster: function()
            {
                return this.subchain(this.getCluster());
            },

            /**
             * @OVERRIDE
             */
            getUri: function()
            {
                return "";
            },

            /**
             * @OVERRIDE
             */
            getType: function()
            {
                return Gitana.TypedIDConstants.TYPE_PLATFORM;
            },

            /**
             * @override
             */
            clone: function()
            {
                return this.getFactory().platform(this.getCluster(), this);
            },

            /** @Override **/
            del: function()
            {
                // not implemented
                return this;
            },

            /** @Override **/
            reload: function()
            {
                var uriFunction = function()
                {
                    return this.getUri() + "/";
                };

                return this.chainReload(null, uriFunction);
            },

            /** @Override **/
            update: function()
            {
                var uriFunction = function()
                {
                    return this.getUri() + "/";
                };

                return this.chainUpdate(null, uriFunction);
            },

            /**
             * Hands back the primary domain instance for this platform.
             *
             * @chained domain
             */
            readPrimaryDomain: function()
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/domains/primary";
                };

                var chainable = this.getFactory().domain(this);
                return this.chainGet(chainable, uriFunction);
            },

            /**
             * Loads information about the platform.
             *
             * @param callback
             */
            loadInfo: function(callback)
            {
                var uriFunction = function()
                {
                    return "/info";
                };

                return this.chainGetResponse(this, uriFunction, {}).then(function(response) {
                    callback(response);
                });
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // REPOSITORIES
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists repositories.
             *
             * @chained repository map
             *
             * @param [Object] pagination pagination (optional)
             */
            listRepositories: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().repositoryMap(this);
                return this.chainGet(chainable, "/repositories", params);
            },

            /**
             * Read a repository.
             *
             * @chained repository
             *
             * @param {String} repositoryId the repository id
             */
            readRepository: function(repositoryId)
            {
                var chainable = this.getFactory().repository(this);
                return this.chainGet(chainable, "/repositories/" + repositoryId);
            },

            /**
             * Create a repository
             *
             * @chained repository
             *
             * @param [Object] object JSON object
             */
            createRepository: function(object)
            {
                var chainable = this.getFactory().repository(this);
                return this.chainCreate(chainable, object, "/repositories");
            },

            /**
             * Queries for a repository.
             *
             * @chained repository map
             *
             * @param {Object} query Query for finding a repository.
             * @param [Object] pagination pagination (optional)
             */
            queryRepositories: function(query, pagination)
            {
                var chainable = this.getFactory().repositoryMap(this);

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainPost(chainable, "/repositories/query", params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type repository.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "domainId": "<domainId>", (optional)
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "domainId": "<domainId>", (optional)
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkRepositoryPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/repositories/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // DOMAINS
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists domains.
             *
             * @chained domain map
             *
             * @param [Object] pagination pagination (optional)
             */
            listDomains: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().domainMap(this);
                return this.chainGet(chainable, "/domains", params);
            },

            /**
             * Read a domain.
             *
             * @chained domain
             *
             * @param {String} domainId the domain id
             */
            readDomain: function(domainId)
            {
                var chainable = this.getFactory().domain(this);
                return this.chainGet(chainable, "/domains/" + domainId);
            },

            /**
             * Create a domain
             *
             * @chained domain
             *
             * @param [Object] object JSON object
             */
            createDomain: function(object)
            {
                var chainable = this.getFactory().domain(this);
                return this.chainCreate(chainable, object, "/domains");
            },

            /**
             * Queries for a domain.
             *
             * @chained domain map
             *
             * @param {Object} query Query for finding a domain.
             * @param [Object] pagination pagination (optional)
             */
            queryDomains: function(query, pagination)
            {
                var chainable = this.getFactory().domainMap(this);

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainPost(chainable, "/domains/query", params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type domain.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkDomainPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/domains/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // VAULTS
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists vaults.
             *
             * @chained vault map
             *
             * @param [Object] pagination pagination (optional)
             */
            listVaults: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().vaultMap(this);
                return this.chainGet(chainable, "/vaults", params);
            },

            /**
             * Read a vault.
             *
             * @chained vault
             *
             * @param {String} vaultId the vault id
             */
            readVault: function(vaultId)
            {
                var chainable = this.getFactory().vault(this);
                return this.chainGet(chainable, "/vaults/" + vaultId);
            },

            /**
             * Create a vault
             *
             * @chained vault
             *
             * @param [Object] object JSON object
             */
            createVault: function(object)
            {
                var chainable = this.getFactory().vault(this);
                return this.chainCreate(chainable, object, "/vaults");
            },

            /**
             * Queries for a vault.
             *
             * @chained vault map
             *
             * @param {Object} query Query for finding a vault.
             * @param [Object] pagination pagination (optional)
             */
            queryVaults: function(query, pagination)
            {
                var chainable = this.getFactory().vaultMap(this);

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainPost(chainable, "/vaults/query", params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type vault.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkVaultPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/vaults/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // AUTHENTICATION METHODS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Logs in as the given user.
             *
             * This delegates a call to the underlying driver.
             *
             * @param {Object} config login config
             * @param [Function] authentication failure handler
             */
            authenticate: function(config, authFailureHandler)
            {
                return this.getDriver().authenticate(config, authFailureHandler);
            },

            /**
             * Clears authentication against the server.
             *
             * @param expireAccessToken (optional, assumed false)
             *
             * @chained server
             *
             * @public
             */
            logout: function(expireAccessToken)
            {
                var self = this;

                return this.subchain().then(function() {

                    var platformCacheKey = this.getDriver().platformCacheKey;
                    if (platformCacheKey)
                    {
                        Gitana.disconnect(platformCacheKey, expireAccessToken);
                    }

                    this.getDriver().clearAuthentication();

                    delete this.getDriver().platformCacheKey;
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // STACKS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the stacks.
             *
             * @param pagination
             *
             * @chained stack map
             */
            listStacks: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().stackMap(this);
                return this.chainGet(chainable, "/stacks", params);
            },

            /**
             * Reads a stack.
             *
             * @param stackId
             *
             * @chained stack
             */
            readStack: function(stackId)
            {
                var chainable = this.getFactory().stack(this);
                return this.chainGet(chainable, "/stacks/" + stackId);
            },

            /**
             * Create a stack
             *
             * @chained stack
             *
             * @param [Object] object JSON object
             */
            createStack: function(object)
            {
                if (!object)
                {
                    object = {};
                }

                var chainable = this.getFactory().stack(this);
                return this.chainCreate(chainable, object, "/stacks");
            },

            /**
             * Queries for stacks.
             *
             * @chained stack map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryStacks: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/stacks/query";
                };

                var chainable = this.getFactory().stackMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Finds a stack for a given data store.
             *
             * @param datastoreType
             * @param datastoreId
             *
             * @chained stack
             */
            findStackForDataStore: function(datastoreType, datastoreId)
            {
                var chainable = this.getFactory().stack(this);
                return this.chainGet(chainable, "/stacks/find/" + datastoreType + "/" + datastoreId);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type stack.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkStackPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/stacks/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // PROJECTS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the projects.
             *
             * @param pagination
             *
             * @chained stack map
             */
            listProjects: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().projectMap(this);
                return this.chainGet(chainable, "/projects", params);
            },

            /**
             * Reads a project.
             *
             * @param projectId
             *
             * @chained project
             */
            readProject: function(projectId)
            {
                var chainable = this.getFactory().project(this);
                return this.chainGet(chainable, "/projects/" + projectId);
            },

            /**
             * Create a project
             *
             * @chained project
             *
             * @param [Object] object JSON object
             */
            createProject: function(object)
            {
                if (!object)
                {
                    object = {};
                }

                var chainable = this.getFactory().project(this);
                return this.chainCreate(chainable, object, "/projects");
            },

            /**
             * Create a project asynchronously.
             * This runs a background job to do the actual project creation and hands back a job ID.
             *
             * @chained project
             *
             * @param [Object] object JSON object
             */
            startCreateProject: function(object, callback)
            {
                var uriFunction = function()
                {
                    return "/projects/start";
                };

                if (!object)
                {
                    object = {};
                }

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {

                    var jobId = response._doc;

                    callback(jobId);
                });
            },

            /**
             * Queries for projects.
             *
             * @chained project map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryProjects: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/projects/query";
                };

                var chainable = this.getFactory().projectMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type project.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkProjectPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/projects/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // PROJECT TYPES
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the project types available for this platform.
             *
             * @chained project type map
             *
             * @param [Object] pagination pagination (optional)
             */
            listProjectTypes: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().projectMap(this);
                return this.chainGet(chainable, "/projecttypes", params);
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // LOGS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Queries for log entries.
             *
             * @chained log entry map
             *
             * @param {Object} query Query for finding log entries.
             * @param [Object] pagination pagination (optional)
             */
            queryLogEntries: function(query, pagination)
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/logs/query";
                };

                if (!query)
                {
                    query = {};
                }

                var chainable = this.getFactory().logEntryMap(this.getCluster());

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Read a log entry.
             *
             * @chained log entry
             *
             * @param {String} logEntryId
             */
            readLogEntry: function(logEntryId)
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/logs/" + logEntryId;
                };

                var chainable = this.getFactory().logEntry(this.getCluster());

                return this.chainGet(chainable, uriFunction);
            },

            /**
             * Reads the last 100 lines of the log as text.
             * The callback receives the text as the argument.
             *
             * @param callback
             */
            readLog: function(callback)
            {
                var self = this;

                var uriFunction = function () {
                    return "/logs/logfile";
                };

                return this.chainGetResponseText(this, uriFunction).then(function(text) {
                    callback.call(this, text);
                });
            },

            /**
             * Creates and reads back a log entry.
             *
             * @param message
             * @param level
             * @param obj
             */
            createLogEntry: function(message, level, obj)
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/logs";
                };

                if (!obj) {
                    obj = {};
                }
                obj.message = message;
                obj.level = level;

                var chainable = this.getFactory().logEntry(this.getCluster());
                return this.chainCreate(chainable, obj, "/logs");
            },

            /**
             * Performs a blind post create of a log entry.  The result is not read back nor handled.
             *
             * @param message
             * @param level
             * @param obj
             */
            postLogEntry: function(message, level, obj)
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/logs";
                };

                if (!obj) {
                    obj = {};
                }
                obj.message = message;
                obj.level = level;

                return this.chainPostEmpty(null, uriFunction, {}, obj, "application/json");
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // REGISTRARS
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists registrars.
             *
             * @chained registrar map
             *
             * @param [Object] pagination pagination (optional)
             */
            listRegistrars: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().registrarMap(this);
                return this.chainGet(chainable, "/registrars", params);
            },

            /**
             * Read a registrar.
             *
             * @chained registrar
             *
             * @param {String} registrarId the registrar id
             */
            readRegistrar: function(registrarId)
            {
                var chainable = this.getFactory().registrar(this);
                return this.chainGet(chainable, "/registrars/" + registrarId);
            },

            /**
             * Create a registrar
             *
             * @chained registrar
             *
             * @param [Object] object JSON object
             */
            createRegistrar: function(object)
            {
                var chainable = this.getFactory().registrar(this);
                return this.chainCreate(chainable, object, "/registrars");
            },

            /**
             * Queries for a registrar.
             *
             * @chained registrar map
             *
             * @param {Object} query Query for finding a vault.
             * @param [Object] pagination pagination (optional)
             */
            queryRegistrars: function(query, pagination)
            {
                var chainable = this.getFactory().registrarMap(this);

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainPost(chainable, "/registrars/query", params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type vault.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkRegistrarPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/registrars/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // APPLICATIONS
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists applications.
             *
             * @chained application map
             *
             * @param [Object] pagination pagination (optional)
             */
            listApplications: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().applicationMap(this);
                return this.chainGet(chainable, "/applications", params);
            },

            /**
             * Read an application.
             *
             * @chained application
             *
             * @param {String} applicationId the application id
             */
            readApplication: function(applicationId)
            {
                var uriFunction = function() {
                    return "/applications/" + applicationId;
                };

                var chainable = this.getFactory().application(this);
                return this.chainGet(chainable, uriFunction);
            },

            /**
             * Create an application
             *
             * @chained application
             *
             * @param [Object] object JSON object
             */
            createApplication: function(object)
            {
                var chainable = this.getFactory().application(this);
                return this.chainCreate(chainable, object, "/applications");
            },

            /**
             * Queries for an application.
             *
             * @chained application map
             *
             * @param {Object} query Query for finding a vault.
             * @param [Object] pagination pagination (optional)
             */
            queryApplications: function(query, pagination)
            {
                var chainable = this.getFactory().applicationMap(this);

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainPost(chainable, "/applications/query", params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type vault.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkApplicationPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/applications/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // APPLICATION TYPES
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the application types available for this platform.
             *
             * @chained application type map
             *
             * @param [Object] pagination pagination (optional)
             */
            listApplicationTypes: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().applicationMap(this);
                return this.chainGet(chainable, "/applicationtypes", params);
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // CLIENTS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the clients.
             *
             * @param pagination
             *
             * @chained client map
             */
            listClients: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().clientMap(this);
                return this.chainGet(chainable, "/clients", params);
            },

            /**
             * Reads a client.
             *
             * @param clientId
             *
             * @chained client
             */
            readClient: function(clientId)
            {
                var chainable = this.getFactory().client(this);
                return this.chainGet(chainable, "/clients/" + clientId);
            },

            /**
             * Create a client
             *
             * @chained client
             *
             * @param [Object] object JSON object
             */
            createClient: function(object)
            {
                if (!object)
                {
                    object = {};
                }

                var chainable = this.getFactory().client(this);
                return this.chainCreate(chainable, object, "/clients");
            },

            /**
             * Queries for clients.
             *
             * @chained client map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryClients: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/clients/query";
                };

                var chainable = this.getFactory().clientMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type client.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkClientPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/clients/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // AUTHENTICATION GRANTS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Reads an authentication grant.
             *
             * @param authenticationGrantId
             *
             * @chained authentication grant
             */
            readAuthenticationGrant: function(authenticationGrantId)
            {
                var chainable = this.getFactory().authenticationGrant(this);
                return this.chainGet(chainable, "/auth/grants/" + authenticationGrantId);
            },

            /**
             * Create an authentication grant
             *
             * @chained authentication grant
             *
             * @param [Object] object JSON object
             */
            createAuthenticationGrant: function(object)
            {
                if (!object)
                {
                    object = {};
                }

                var chainable = this.getFactory().authenticationGrant(this);
                return this.chainCreate(chainable, object, "/auth/grants");
            },

            /**
             * Queries for authentication grants.
             *
             * @chained authentication grant map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryAuthenticationGrants: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/auth/grants/query";
                };

                var chainable = this.getFactory().authenticationGrantMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type authentication grant.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkAuthenticationGrantPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/auth/grants/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // DIRECTORIES
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists directories.
             *
             * @chained directory map
             *
             * @param [Object] pagination pagination (optional)
             */
            listDirectories: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().directoryMap(this);
                return this.chainGet(chainable, "/directories", params);
            },

            /**
             * Read a directory.
             *
             * @chained directory
             *
             * @param {String} directoryId the directory id
             */
            readDirectory: function(directoryId)
            {
                var chainable = this.getFactory().directory(this);
                return this.chainGet(chainable, "/directories/" + directoryId);
            },

            /**
             * Create a directory.
             *
             * @chained directory
             *
             * @param [Object] object JSON object
             */
            createDirectory: function(object)
            {
                var chainable = this.getFactory().directory(this);
                return this.chainCreate(chainable, object, "/directories");
            },

            /**
             * Queries for a directory.
             *
             * @chained directory map
             *
             * @param {Object} query Query for finding a directory.
             * @param [Object] pagination pagination (optional)
             */
            queryDirectories: function(query, pagination)
            {
                var chainable = this.getFactory().directoryMap(this);

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainPost(chainable, "/directories/query", params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type directory.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkDirectoryPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/directories/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // BILLING PROVIDER CONFIGURATIONS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the billing provider configurations.
             *
             * @param pagination
             *
             * @chained billing provider configuration map
             */
            listBillingProviderConfigurations: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().billingProviderConfigurationMap(this);
                return this.chainGet(chainable, "/billing/configurations", params);
            },

            /**
             * Reads a billing provider configuration.
             *
             * @param billingProviderConfigurationId
             *
             * @chained billing provider configuration
             */
            readBillingProviderConfiguration: function(billingProviderConfigurationId)
            {
                var chainable = this.getFactory().billingProviderConfiguration(this);
                return this.chainGet(chainable, "/billing/configurations/" + billingProviderConfigurationId);
            },

            /**
             * Create a billing provider configuration.
             *
             * @chained billing provider configuration
             *
             * @param {String} providerId
             * @param [Object] object JSON object
             */
            createBillingProviderConfiguration: function(providerId, object)
            {
                if (!object)
                {
                    object = {};
                }
                object["providerId"] = providerId;

                var chainable = this.getFactory().billingProviderConfiguration(this);
                return this.chainCreate(chainable, object, "/billing/configurations");
            },

            /**
             * Queries for billing provider configurations.
             *
             * @chained billing provider configuration map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryBillingProviderConfigurations: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/billing/configurations/query";
                };

                var chainable = this.getFactory().billingProviderConfigurationMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type billing provider configuration.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkBillingProviderConfigurationPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/billing/configurations/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // WEB HOSTS
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists web hosts.
             *
             * @chained web host map
             *
             * @param [Object] pagination pagination (optional)
             */
            listWebHosts: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().webhostMap(this);
                return this.chainGet(chainable, "/webhosts", params);
            },

            /**
             * Read a web host
             *
             * @chained web host
             *
             * @param {String} webhostId the web host id
             */
            readWebHost: function(webhostId)
            {
                var chainable = this.getFactory().webhost(this);
                return this.chainGet(chainable, "/webhosts/" + webhostId);
            },

            /**
             * Create a web host.
             *
             * @chained web host
             *
             * @param [Object] object JSON object
             */
            createWebHost: function(object)
            {
                var chainable = this.getFactory().webhost(this);
                return this.chainCreate(chainable, object, "/webhosts");
            },

            /**
             * Queries for web hosts.
             *
             * @chained web host map
             *
             * @param {Object} query Query for finding web hosts.
             * @param [Object] pagination pagination (optional)
             */
            queryWebHosts: function(query, pagination)
            {
                var chainable = this.getFactory().webhostMap(this);

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainPost(chainable, "/webhosts/query", params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type web host.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkWebHostPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/webhosts/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // WARE HOUSES
            //
            /////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists warehouses
             *
             * @chained warehouse map
             *
             * @param [Object] pagination pagination (optional)
             */
            listWarehouses: function(pagination)
            {
                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().warehouseMap(this);
                return this.chainGet(chainable, "/warehouses", params);
            },

            /**
             * Read a warehouse
             *
             * @chained warehouse
             *
             * @param {String} warehouseId
             */
            readWarehouse: function(warehouseId)
            {
                var chainable = this.getFactory().warehouse(this);
                return this.chainGet(chainable, "/warehouses/" + warehouseId);
            },

            /**
             * Create a warehouse.
             *
             * @chained warehouse
             *
             * @param [Object] object JSON object
             */
            createWarehouse: function(object)
            {
                var chainable = this.getFactory().warehouse(this);
                return this.chainCreate(chainable, object, "/warehouses");
            },

            /**
             * Queries for warehouses
             *
             * @chained warehouse map
             *
             * @param {Object} query Query for finding warehouses.
             * @param [Object] pagination pagination (optional)
             */
            queryWarehouses: function(query, pagination)
            {
                var chainable = this.getFactory().warehouseMap(this);

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainPost(chainable, "/warehouses/query", params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type warehouse
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkWarehousePermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/warehouses/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // CURRENT TENANT ATTACHMENTS
            //
            //////////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Hands back a map of attachments for the platform's parent tenant.
             *
             * @chained attachment map
             *
             * @public
             */
            listTenantAttachments: function()
            {
                var self = this;

                // we bind the attachment map to a modified copy of platform with the URI adjusted
                // so that it forms "/tenant/attachments/<attachmentId>" for any lookups
                var pseudoTenant = this.clone();
                pseudoTenant.getUri = function () {
                    return "/tenant";
                };

                var result = this.subchain(new Gitana.BinaryAttachmentMap(pseudoTenant));
                result.then(function() {

                    var chain = this;

                    self.getDriver().gitanaGet(self.getUri() + "/tenant/attachments", null, {}, function(response) {
                        chain.handleResponse(response);
                        chain.next();
                    });

                    return false;
                });

                return result;
            },

            /**
             * Picks off a single attachment from this platform's parent tenant
             *
             * @chained attachment
             *
             * @param attachmentId
             */
            tenantAttachment: function(attachmentId)
            {
                return this.listTenantAttachments().select(attachmentId);
            },

            /**
             * Creates an attachment to this platform's parent tenant.
             *
             * When using this method from within the JS driver, it really only works for text-based content such
             * as JSON or text.
             *
             * @chained attachment
             *
             * @param attachmentId (use null or false for default attachment)
             * @param contentType
             * @param data
             */
            tenantAttach: function(attachmentId, contentType, data)
            {
                var self = this;

                var tenant = this.clone();
                tenant.getUri = function () {
                    return "/tenant";
                };

                // the thing we're handing back
                var result = this.subchain(new Gitana.BinaryAttachment(tenant));

                // preload some work onto a subchain
                result.subchain().then(function() {

                    // upload the attachment
                    var uploadUri = self.getUri() + "/tenant/attachments/" + attachmentId;
                    this.chainUpload(this, uploadUri, null, contentType, data).then(function() {

                        // read back attachment information and plug onto result
                        this.subchain(self).listTenantAttachments().then(function() {
                            this.select(attachmentId).then(function() {
                                result.handleResponse(this);
                            });
                        });
                    });
                });

                return result;
            },

            /**
             * Deletes an attachment from this platform's parent tenant.
             *
             * @param attachmentId
             */
            tenantUnattach: function(attachmentId)
            {
                return this.subchain().then(function() {

                    this.chainDelete(this, this.getUri() + "/tenant/attachments/" + attachmentId).then(function() {

                        // TODO

                    });
                });
            },

            /**
             * Generates a URI to a preview resource.
             */
            getTenantPreviewUri: Gitana.Methods.getPreviewUri("tenant/preview"),

            /**
             * Connects to a specific application on the platform.  Preloads any application data and stack information
             * and then fires into a callback with context set to application helper.
             *
             * @param settings
             * @param callback
             */
            app: function(settings, callback)
            {
                var self = this;

                // support for null appkey
                if (Gitana.isFunction(settings)) {
                    callback = settings;
                    settings = null;
                }

                if (Gitana.isString(settings)) {
                    settings = { "application": settings };
                }

                // build preload config
                var config = {
                    "application": null,
                    "appCacheKey": null
                };
                Gitana.copyKeepers(config, Gitana.loadDefaultConfig());
                Gitana.copyKeepers(config, self.getDriver().getOriginalConfiguration());
                Gitana.copyKeepers(config, settings);

                // is this app context already cached?
                //var cacheKey = self.getId() + "/" + config.application;
                var cacheKey = config.appCacheKey;
                if (cacheKey)
                {
                    if (Gitana.APPS && Gitana.APPS[cacheKey])
                    {
                        callback.call(Chain(Gitana.APPS[cacheKey]));
                        return;
                    }
                }

                if (!config.application) {
                    callback.call(self, new Error("No application configured"));
                    return;
                }

                // load and cache
                var helper = new Gitana.AppHelper(self, config);
                if (!Gitana.APPS) {
                    Gitana.APPS = {};
                }

                helper.init.call(helper, function(err) {

                    if (err)
                    {
                        callback(err);
                        return;
                    }

                    if (cacheKey)
                    {
                        Gitana.APPS[cacheKey] = helper;
                    }

                    callback.call(Chain(helper));
                });
            },

            /**
             * Retrieves authorities and permissions for multiple reference/principal combinations.
             *
             * Example of entries array:
             *
             * [{
             *    "permissioned": "<permissionedReference>",
             *    "principalId": "<principalId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissioned": "<permissionedReference>",
             *    "principalId": "<principalId>",
             *    "authorities": [...],
             *    "permissions": [...]
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            accessLookups: function(entries, callback)
            {
                var uriFunction = function()
                {
                    return "/access/lookup";
                };

                var object = {
                    "entries": entries
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["entries"]);
                });
            },

            /**
             * Retrieves authorities and permissions for multiple reference/principal combinations.
             *
             * Example of entries array:
             *
             * [{
             *    "permissioned": "<permissionedReference>",
             *    "principalId": "<principalId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissioned": "<permissionedReference>",
             *    "principalId": "<principalId>",
             *    "permissionId|authorityId": "<permissionId|authorityId>",
             *    "hasPermission|hasAuthority": true | false
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            accessChecks: function(entries, callback)
            {
                var uriFunction = function()
                {
                    return "/access/check";
                };

                var object = {
                    "entries": entries
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["entries"]);
                });
            },

            /**
             * Reads one or more referenceable objects by reference id.
             *
             * Example of entries array:
             *
             * [{
             *    "ref": "<reference>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "ref": "<reference>",
             *    "entry": { ... object }
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            referenceReads: function(entries, callback)
            {
                var uriFunction = function()
                {
                    return "/ref/read";
                };

                var object = {
                    "entries": entries
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["entries"]);
                });
            },

            /**
             * Calculates the JSON Patch diff between two objects.
             *
             * @param sourceRef
             * @param targetRef
             *
             * @param callback
             */
            referenceDiff: function(sourceRef, targetRef, callback)
            {
                var uriFunction = function()
                {
                    return "/ref/diff";
                };

                var params = {
                    "source": sourceRef,
                    "target": targetRef
                };

                return this.chainGetResponse(this, uriFunction, params).then(function(response) {
                    callback.call(this, response);
                });
            },

            /**
             * Applies a JSON Patch to an object to produce a patched object.
             *
             * @param sourceRef
             * @param diffObject
             *
             * @param callback
             */
            referenceMerge: function(sourceRef, diffObject, callback)
            {
                var uriFunction = function()
                {
                    return "/ref/merge";
                };

                var params = {
                    "source": sourceRef
                };

                return this.chainPostResponse(this, uriFunction, params, diffObject).then(function(response) {
                    callback.call(this, response);
                });
            },

            ///////////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // ADMIN
            //
            ///////////////////////////////////////////////////////////////////////////////////////////////////////

            adminIndexDatastores: function()
            {
                var self = this;

                return this.then(function() {

                    var chain = this;

                    // call
                    var uri = self.getUri() + "/admin/index";
                    self.getDriver().gitanaPost(uri, null, {}, function(response) {
                        chain.next();
                    });

                    // NOTE: we return false to tell the chain that we'll manually call next()
                    return false;
                });
            },

            adminRepair: function()
            {
                var self = this;

                return this.then(function() {

                    var chain = this;

                    // call
                    var uri = self.getUri() + "/admin/repair";
                    self.getDriver().gitanaPost(uri, null, {}, function(response) {
                        chain.next();
                    });

                    // NOTE: we return false to tell the chain that we'll manually call next()
                    return false;
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // RULE
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the available rule actions.
             *
             * @param pagination
             *
             * @chained action descriptor map
             */
            listRuleActions: function(pagination, callback)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/rule/actions";
                };

                return this.chainGetResponse(this, uriFunction, params).then(function(response) {
                    callback(response);
                });
            },

            /**
             * Reads a rule action.
             *
             * @param actioqnId
             *
             * @chained a
             */
            readRuleAction: function(actionId, callback)
            {
                var uriFunction = function()
                {
                    return "/rule/actions/" + actionId;
                };

                return this.chainGetResponse(this, uriFunction, {}).then(function(response) {
                    callback(response);
                });
            },

            /**
             * Lists the available rule conditions.
             *
             * @param pagination
             *
             * @chained condition descriptor map
             */
            listRuleConditions: function(pagination, callback)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/rule/conditions";
                };

                return this.chainGetResponse(this, uriFunction, params).then(function(response) {
                    callback(response);
                });
            },

            /**
             * Reads a rule condition.
             *
             * @param conditionId
             *
             * @chained a
             */
            readRuleCondition: function(conditionId, callback)
            {
                var uriFunction = function()
                {
                    return "/rule/conditions/" + conditionId;
                };

                return this.chainGetResponse(this, uriFunction, {}).then(function(response) {
                    callback(response);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // WORKFLOW MODELS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the deployed workflow models.
             *
             * @param pagination
             *
             * @chained workflow model map
             */
            listWorkflowModels: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().workflowModelMap(this);
                return this.chainGet(chainable, "/workflow/models", params);
            },

            /**
             * Lists all workflow models.
             *
             * @param pagination
             *
             * @chained workflow model map
             */
            listAllWorkflowModels: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().workflowModelMap(this);
                return this.chainGet(chainable, "/workflow/models?all=true", params);
            },

            /**
             * Reads a workflow model.
             *
             * @param {String} workflowModelId
             * @param [String] workflowModelVersionId
             *
             * @chained workflowModel
             */
            readWorkflowModel: function(workflowModelId, workflowModelVersionId)
            {
                var uriFunction = function()
                {
                    var url = "/workflow/models/" + workflowModelId;
                    if (workflowModelVersionId)
                    {
                        url += "/versions/" + workflowModelVersionId;
                    }

                    return url;
                };

                var chainable = this.getFactory().workflowModel(this);
                return this.chainGet(chainable, uriFunction);
            },

            /**
             * Create a workflow model
             *
             * @chained workflow model
             *
             * @param {String} id
             * @param [Object] object JSON object
             */
            createWorkflowModel: function(id, object)
            {
                if (!object)
                {
                    object = {};
                }

                object.id = id;

                var chainable = this.getFactory().workflowModel(this);
                return this.chainCreate(chainable, object, "/workflow/models");
            },

            /**
             * Queries for deployed workflow models.
             *
             * @chained workflow model map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryWorkflowModels: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/workflow/models/query";
                };

                var chainable = this.getFactory().workflowModelMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Queries for all (deployed and not deployed) workflow models.
             *
             * @chained workflow model map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryAllWorkflowModels: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/workflow/models/query?all=true";
                };

                var chainable = this.getFactory().workflowModelMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type workflow model.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkWorkflowModelPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/workflow/models/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /**
             * Lists the workflow model versions.
             *
             * @param id
             * @param pagination
             *
             * @chained workflow model map
             */
            listWorkflowModelVersions: function(id, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    var self = this;

                    return "/workflow/models/" + id + "/versions";
                };

                var chainable = this.getFactory().workflowModelMap(this);
                return this.chainGet(chainable, uriFunction, params);
            },

            /**
             * Queries for workflow model versions.
             *
             * @chained workflow model map
             *
             * @param {String} id
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryWorkflowModelVersions: function(id, query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    var self = this;

                    return "/workflow/models/" + id + "/versions/query";
                };

                var chainable = this.getFactory().workflowModelMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // WORKFLOW INSTANCES
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the workflows.
             *
             * @param pagination
             *
             * @chained client map
             */
            listWorkflows: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().workflowInstanceMap(this);
                return this.chainGet(chainable, "/workflow/instances", params);
            },

            /**
             * Reads a workflow.
             *
             * @param workflowId
             *
             * @chained workflow
             */
            readWorkflow: function(workflowId)
            {
                var chainable = this.getFactory().workflowInstance(this);
                return this.chainGet(chainable, "/workflow/instances/" + workflowId);
            },

            /**
             * Create a workflow
             *
             * @chained workflow
             *
             * @param {String} workflowModelId workflow id
             * @param [Object] object JSON object
             */
            createWorkflow: function(workflowModelId, object)
            {
                if (!object)
                {
                    object = {};
                }

                var params = {
                    "modelId": workflowModelId
                };

                var chainable = this.getFactory().workflowInstance(this);
                return this.chainCreate(chainable, object, "/workflow/instances", params);
            },

            /**
             * Queries for workflows.
             *
             * @chained workflow map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryWorkflows: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/workflow/instances/query";
                };

                var chainable = this.getFactory().workflowInstanceMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type workflow instance.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkWorkflowInstancePermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/workflow/instance/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // WORKFLOW TASKS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the workflow tasks.
             *
             * @param pagination
             *
             * @chained workflow task map
             */
            listWorkflowTasks: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().workflowTaskMap(this);
                return this.chainGet(chainable, "/workflow/tasks", params);
            },

            /**
             * Reads a workflow task.
             *
             * @param workflowTaskId
             *
             * @chained workflow task
             */
            readWorkflowTask: function(workflowTaskId)
            {
                var chainable = this.getFactory().workflowTask(this);
                return this.chainGet(chainable, "/workflow/tasks/" + workflowTaskId);
            },

            /**
             * Queries for workflow tasks.
             *
             * @chained workflow task map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryWorkflowTasks: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/workflow/tasks/query";
                };

                var chainable = this.getFactory().workflowTaskMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type workflow task.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkWorkflowTaskPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/workflow/task/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // WORKFLOW COMMENTS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the workflow comments.
             *
             * @param pagination
             *
             * @chained workflow comment map
             */
            listWorkflowComments: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().workflowCommentMap(this);
                return this.chainGet(chainable, "/workflow/comments", params);
            },

            /**
             * Reads a workflow comment.
             *
             * @param workflowCommentId
             *
             * @chained workflow comment
             */
            readWorkflowComment: function(workflowCommentId)
            {
                var chainable = this.getFactory().workflowComment(this);
                return this.chainGet(chainable, "/workflow/comments/" + workflowCommentId);
            },

            /**
             * Create a workflow comment
             *
             * @chained workflow comment
             *
             * @param {String} workflowId
             * @param [String] workflowTaskId
             * @param [Object] object JSON object
             */
            createWorkflowComment: function(workflowId, workflowTaskId, object)
            {
                var params = {};

                var createUri = function()
                {
                    var uri = "/workflow/instances/" + workflowId + "/comments";
                    if (workflowTaskId)
                    {
                        uri += "?taskId=" + workflowTaskId;
                    }

                    return uri;
                };

                var readUri = function(status)
                {
                    return "/workflow/comments/" + status._doc;
                };

                var chainable = this.getFactory().workflowComment(this);

                return this.chainCreateEx(chainable, object, createUri, readUri);
            },

            /**
             * Queries for workflow comments.
             *
             * @chained workflow comment map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryWorkflowComments: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/workflow/comments/query";
                };

                var chainable = this.getFactory().workflowCommentMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type workflow task.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkWorkflowCommentPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/workflow/comments/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // WORKFLOW TASKS - CURRENT USER
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists tasks for the current user.
             *
             * @param filter empty or "assigned" or "unassigned"
             * @param pagination
             *
             * @returns {*}
             */
            listTasksForCurrentUser: function(filter, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                if (filter)
                {
                    params.filter = filter;
                }

                var chainable = this.getFactory().workflowTaskMap(this);
                return this.chainGet(chainable, "/workflow/user/tasks", params);
            },

            /**
             * Lists tasks for the current user.
             *
             * @param filter empty or "assigned" or "unassigned"
             * @param query
             * @param pagination
             *
             * @returns {*}
             */
            queryTasksForCurrentUser: function(filter, query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                if (filter)
                {
                    params.filter = filter;
                }

                var chainable = this.getFactory().workflowTaskMap(this);
                return this.chainPost(chainable, "/workflow/user/tasks/query", params, query);
            },

            queryTasks: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().workflowTaskMap(this);
                return this.chainPost(chainable, "/workflow/tasks/query", params, query);
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // WORKFLOW HISTORY
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Loads the history for a workflow.
             *
             * @param workflowId the id of the workflow to load the history for
             * @param workflowTaskId the current workflow task (or null if full history)
             * @param pagination
             * @param callback
             */
            loadWorkflowHistory: function(workflowId, pagination, callback)
            {
                var uriFunction = function()
                {
                    return "/workflow/instances/" + workflowId + "/history";
                };

                // prepare params (with pagination)
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                return this.chainGetResponse(this, uriFunction, params).then(function(response) {
                    callback(response);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // SCHEDULED WORK ITEMS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the scheduled work items.
             *
             * @param pagination
             *
             * @chained scheduled work item map
             */
            listScheduledWorkItems: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().scheduledWorkMap(this);
                return this.chainGet(chainable, "/work/scheduled", params);
            },

            /**
             * Reads a scheduled work item.
             *
             * @param scheduledWorkId
             *
             * @chained scheduled work
             */
            readScheduledWorkItem: function(scheduledWorkId)
            {
                var chainable = this.getFactory().scheduledWork(this);
                return this.chainGet(chainable, "/work/scheduled/" + scheduledWorkId);
            },

            /**
             * Create a scheduled work item
             *
             * @chained scheduled work
             *
             * @param [Object] object JSON object
             */
            createScheduledWorkItem: function(object)
            {
                if (!object)
                {
                    object = {};
                }

                var chainable = this.getFactory().scheduledWork(this);
                return this.chainCreate(chainable, object, "/work/scheduled");
            },

            /**
             * Queries for scheduled work items.
             *
             * @chained scheduled work item map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryScheduledWorkItems: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/work/scheduled/query";
                };

                var chainable = this.getFactory().scheduledWorkMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type scheduled work.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkScheduledWorkPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/work/scheduled/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // REPORTS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the reports.
             *
             * @param pagination
             *
             * @chained scheduled work item map
             */
            listReports: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().reportMap(this);
                return this.chainGet(chainable, "/reports", params);
            },

            /**
             * Reads a report.
             *
             * @param reportId
             *
             * @chained report
             */
            readReport: function(reportId)
            {
                var chainable = this.getFactory().report(this);
                return this.chainGet(chainable, "/reports/" + reportId);
            },

            /**
             * Create a report
             *
             * @chained report
             *
             * @param [Object] object JSON object
             */
            createReport: function(object)
            {
                if (!object)
                {
                    object = {};
                }

                var chainable = this.getFactory().report(this);
                return this.chainCreate(chainable, object, "/reports");
            },

            /**
             * Queries for reports.
             *
             * @chained report map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryReports: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/reports/query";
                };

                var chainable = this.getFactory().reportMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type report.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkReportPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/reports/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            },

            /**
             * Executes a report.
             *
             * @chained report
             *
             * @param {String} reportId the id of the report to run
             * @param [Object] config additional config
             * @param {Function} callback callback to fire
             */
            executeReport: function(reportId, config, callback)
            {
                if (typeof(config) == "function")
                {
                    callback = config;
                    config = {};
                }

                var uriFunction = function()
                {
                    return "/reports/" + reportId + "/execute";
                };

                return this.chainPostResponse(this, uriFunction, null, config).then(function(response) {
                    callback.call(this, response);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // EXPORTS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Runs an export and waits for the export to complete.
             *
             * This runs an asynchronous background poll checking status for the job to complete.
             * Once complete, the exportId and status are passed to the callback.
             *
             * @param objects
             * @param configuration
             * @param callback
             * @returns {*}
             */
            runExport: function(objects, configuration, callback)
            {
                var self = this;

                var uriFunction = function()
                {
                    return "/ref/exports/start";
                };

                if (!configuration)
                {
                    configuration = {};
                }

                var references = [];
                if (objects.refs)
                {
                    references = objects.refs();
                }
                else if (objects.length)
                {
                    for (var i = 0; i < objects.length; i++)
                    {
                        references.push(objects[i].ref());
                    }
                }
                configuration.references = references;

                var chainable = this;

                return this.chainPostResponse(this, uriFunction, {}, configuration).then(function(response) {

                    var exportId = response._doc;

                    // wait for the export to finish...
                    var f = function()
                    {
                        window.setTimeout(function() {

                            Chain(chainable).readExportStatus(exportId, function(status) {
                                if (status.state == "FINISHED") {
                                    callback(exportId, status);
                                    chainable.next();
                                } else if (status.state == "ERROR") {
                                    callback(exportId, status);
                                    chainable.next();
                                } else {
                                    f();
                                }
                            });

                        }, 1000);
                    };
                    f();

                    return false;

                });
            },

            /**
             * Retrieves the status for a running export job.
             * The status includes the "fileCount" field which indicates the total number of exported files.
             *
             * @param exportId
             * @param callback
             * @returns {*}
             */
            readExportStatus: function(exportId, callback)
            {
                var uriFunction = function()
                {
                    return "/ref/exports/" + exportId + "/status";
                };

                return this.chainGetResponse(this, uriFunction).then(function(response) {
                    callback(response);
                });
            },

            /**
             * Gets the download URL for a completed export.
             *
             * @param exportId
             * @param index
             * @param useDispositionHeader
             * @returns {string}
             */
            exportDownloadUrl: function(exportId, index, useDispositionHeader)
            {
                var url = "/ref/exports/" + exportId + "/download";

                if (index)
                {
                    url += "/" + index;
                }

                if (useDispositionHeader)
                {
                    url += "?a=true";
                }

                return url;
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // EXTERNAL SERVICE DESCRIPTORS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the service descriptors.
             *
             * @param pagination
             *
             * @chained descriptor map
             */
            listServiceDescriptors: function(pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var chainable = this.getFactory().descriptorMap(this);
                return this.chainGet(chainable, "/descriptors", params);
            },

            /**
             * Reads a service descriptor.
             *
             * @param descriptorId
             *
             * @chained descriptor
             */
            readServiceDescriptor: function(descriptorId)
            {
                var chainable = this.getFactory().descriptor(this);
                return this.chainGet(chainable, "/descriptors/" + descriptorId);
            },

            /**
             * Create a service descriptor
             *
             * @chained descriptor
             *
             * @param [Object] object JSON object
             */
            createServiceDescriptor: function(object)
            {
                if (!object)
                {
                    object = {};
                }

                var chainable = this.getFactory().descriptor(this);
                return this.chainCreate(chainable, object, "/descriptors");
            },

            /**
             * Queries for descriptors.
             *
             * @chained descriptor map
             *
             * @param {Object} query
             * @param [Object] pagination pagination (optional)
             */
            queryServiceDescriptors: function(query, pagination)
            {
                var params = {};
                if (pagination)
                {
                    Gitana.copyInto(params, pagination);
                }

                var uriFunction = function()
                {
                    return "/descriptors/query";
                };

                var chainable = this.getFactory().descriptorMap(this);
                return this.chainPost(chainable, uriFunction, params, query);
            },

            /**
             * Performs a bulk check of permissions against permissioned objects of type external service descriptor.
             *
             * Example of checks array:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>"
             * }]
             *
             * The callback receives an array of results, example:
             *
             * [{
             *    "permissionedId": "<permissionedId>",
             *    "principalId": "<principalId>",
             *    "permissionId": "<permissionId>",
             *    "result": true
             * }]
             *
             * The order of elements in the array will be the same for checks and results.
             *
             * @param checks
             * @param callback
             */
            checkServiceDescriptorPermissions: function(checks, callback)
            {
                var uriFunction = function()
                {
                    return "/descriptors/permissions/check";
                };

                var object = {
                    "checks": checks
                };

                return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
                    callback.call(this, response["results"]);
                });
            }

        });

    })(window);