diff --git a/.gitignore b/.gitignore
index 2556d792..606ad516 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,5 @@
/hooks
/out
.idea
-*.iml
\ No newline at end of file
+*.iml
+dist
diff --git a/Gruntfile.js b/Gruntfile.js
index 39c60592..32ec3873 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,36 +1,39 @@
-module.exports = function(grunt) {
+module.exports = function (grunt) {
- var fs = require("fs");
- var path = require("path");
+ const fs = require("fs");
+ const path = require("path");
- var WEB_SERVER_HOST = "test.cloudcms.com";
- var WEB_SERVER_PORT = 8000;
- var WEB_SERVER_BASE_PATH = ".";
+ const WEB_SERVER_HOST = "0.0.0.0";
+ const WEB_SERVER_PORT = 8000;
+ const WEB_SERVER_BASE_PATH = ".";
- var PROXY_HOST = "test.cloudcms.com";
- var PROXY_PORT = 8080;
- var PROXY_TIMEOUT = 5 * 60 * 1000;
+ const PROXY_HOST = "test.cloudcms.net";
+ const PROXY_PORT = 8080;
+ const PROXY_TIMEOUT = 5 * 60 * 1000;
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-connect-proxy');
- grunt.loadNpmTasks('grunt-closure-compiler');
grunt.loadNpmTasks('grunt-jsdoc');
grunt.loadNpmTasks('grunt-aws-s3');
grunt.loadNpmTasks('grunt-invalidate-cloudfront');
grunt.loadNpmTasks('grunt-release');
grunt.loadNpmTasks('grunt-bumpup');
+ grunt.loadNpmTasks('grunt-contrib-jshint');
+ grunt.loadNpmTasks('grunt-contrib-qunit');
+ grunt.loadNpmTasks('grunt-exec');
+ grunt.loadNpmTasks('grunt-terser');
// register one or more task lists (you should ALWAYS have a "default" task list)
- grunt.registerTask('test', ['configureProxies:testing', 'connect:testing', 'qunit']);
+ grunt.registerTask('test', ['configureProxies:testing' ,'connect:testing', 'qunit']);
grunt.registerTask('web', ['configureProxies:standalone', 'connect:standalone']);
- grunt.registerTask('closure', ['closure-compiler']);
grunt.registerTask('cdn', ['aws_s3:clean_version', 'aws_s3:clean_latest', 'aws_s3:publish_version', 'aws_s3:publish_latest', 'invalidate_cloudfront:production_version', 'invalidate_cloudfront:production_latest']);
grunt.registerTask('bump', ['bumpup', 'writeVersionProperties']);
+ grunt.registerTask('clean-package-compress', ['exec:antClean', 'exec:antPackage', 'terser']);
- var pkg = grunt.file.readJSON('package.json');
+ const pkg = grunt.file.readJSON('package.json');
// aws configuration
- var awsConfig = {
+ let awsConfig = {
cloudfrontDistributionIds: []
};
try {
@@ -39,56 +42,54 @@ module.exports = function(grunt) {
}
// github configuration
- var githubConfig = {};
+ let githubConfig = {};
try {
githubConfig = grunt.file.readJSON("../settings/__github.json");
+ } catch (e) {
}
- catch (e) {}
process.env.GITHUB_USERNAME = githubConfig.username;
process.env.GITHUB_PASSWORD = githubConfig.password;
- var name = "gitana-javascript-driver";
-
+ const name = "gitana-javascript-driver";
+
// injects a proxy into the middleware stack
- var middleware = function(connect, options)
- {
- // default
- var middlewares = [];
- var directory = options.directory || options.base[options.base.length - 1];
+ middleware = function (connect, options) {
+
+ // Setup the proxy
+ const middlewares = [require('grunt-connect-proxy/lib/utils').proxyRequest];
+
+ // Serve static files.
if (!Array.isArray(options.base)) {
options.base = [options.base];
}
options.base.forEach(function(base) {
- // Serve static files.
middlewares.push(connect.static(base));
});
+
// Make directory browse-able.
+ const directory = options.directory || options.base[options.base.length - 1];
middlewares.push(connect.directory(directory));
-
- // push our proxy logic ahead on the middlewares
- var proxy = require('grunt-connect-proxy/lib/utils').proxyRequest;
- middlewares.unshift(proxy);
-
+
return middlewares;
};
- grunt.registerTask("writeVersionProperties", "Writes a version.properties file for ant to pick up", function() {
+ grunt.registerTask("writeVersionProperties", "Writes a version.properties file for ant to pick up", function () {
- var pkg = grunt.file.readJSON('package.json');
- var version = pkg.version;
+ const pkg = grunt.file.readJSON('package.json');
+ const version = pkg.version;
grunt.file.delete("version.properties");
fs.writeFileSync("version.properties", "version=" + version);
});
// applies some cosmetic spacing
- grunt.event.on('qunit.begin', function (url) {
+ grunt.event.on('qunit.begin', function () {
grunt.log.ok("");
});
// config
- grunt.initConfig({
+ grunt.config.init({
"qunit": {
"all": {
@@ -127,6 +128,7 @@ module.exports = function(grunt) {
},
"testing": {
"options": {
+ "logger": "dev",
"base": WEB_SERVER_BASE_PATH,
"hostname": WEB_SERVER_HOST,
"port": WEB_SERVER_PORT,
@@ -148,20 +150,20 @@ module.exports = function(grunt) {
},
"jshint": {
- "gitana": {
- "options": {
- "multistr": true,
- "scripturl": true,
- "laxcomma": true,
- "-W069": true, // "['variable'] is better written in dot notation
- "-W041": true, // "Use '===' to compare with null or 0
- "-W004": true, // duplicate variables
- "-W014": true, // line breaking +
- "-W065": true, // radix
- "-W083": true // functions in loops
- },
- src: ["js/gitana/**/*.js"]
- }
+ "options": {
+ "multistr": true,
+ "scripturl": true,
+ "laxcomma": true,
+ "-W069": true, // "["constiable"] is better written in dot notation
+ "-W041": true, // "Use "===" to compare with null or 0
+ "-W004": true, // duplicate constiables
+ "-W014": true, // line breaking +
+ "-W065": true, // radix
+ "-W083": true, // functions in loops
+ "esversion": 6
+ },
+ all: ["js/gitana/**/*.js"]
+
},
"closure-compiler": {
@@ -298,6 +300,28 @@ module.exports = function(grunt) {
"passwordVar": "GITHUB_PASSWORD"
}
}
+ },
+ "exec": {
+ "antClean": {
+ "command": "ant clean"
+ },
+ "antPackage": {
+ "command": "ant package"
+ }
+ },
+ "terser": {
+ "options": {
+ "compress": {
+ "passes": 3
+ },
+ "ecma": 6,
+ "output": {
+ "beautify": false
+ },
+ "toplevel": true,
+ "module": true
+ },
+ "./dist/gitana.min.js": ["./js/gitana/**/*.js"]
}
});
diff --git a/README.md b/README.md
index 60c38819..d0845206 100644
--- a/README.md
+++ b/README.md
@@ -149,7 +149,7 @@ This will install the latest versions of Grunt and all Grunt and Node.js depende
Then, to build, run the following:
````
-ant clean package
+grunt clean-package-compress
````
This will produce the latest gitana.js and gitana.min.js files in your dist
diff --git a/bower.json b/bower.json
index cf7a5ad3..55d2a4d0 100644
--- a/bower.json
+++ b/bower.json
@@ -1,7 +1,7 @@
{
"name": "gitana",
"description": "Gitana JavaScript Driver for Cloud CMS",
- "version": "1.0.305",
+ "version": "1.0.307",
"main": "dist/gitana.js",
"license": "Apache-2.0",
"keywords": [
diff --git a/build.xml b/build.xml
index 9aea9999..7758c07b 100644
--- a/build.xml
+++ b/build.xml
@@ -13,8 +13,6 @@
-
-
@@ -314,24 +312,7 @@
-
- Compressing Javascript...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/component.json b/component.json
index b5c53167..ddb649a8 100644
--- a/component.json
+++ b/component.json
@@ -1,6 +1,6 @@
{
"name": "gitana",
- "version": "1.0.305",
+ "version": "1.0.307",
"main": [
"dist/gitana.js"
],
diff --git a/dist/gitana.js b/dist/gitana.js
new file mode 100644
index 00000000..7f1e87cf
--- /dev/null
+++ b/dist/gitana.js
@@ -0,0 +1,35962 @@
+/*
+Gitana JavaScript Driver - Version 1.0.304
+
+Copyright 2019 Gitana Software, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+For more information, please contact Gitana Software, Inc. at this
+address:
+
+ info@cloudcms.com
+*/
+/**
+ * UMD wrapper for compatibility with browser, Node and AMD.
+ *
+ * Based on:
+ * https://github.com/umdjs/umd/blob/master/returnExports.js
+ */
+(function (root, factory)
+{
+ if (typeof exports === 'object')
+ {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ //module.exports = factory(require('b'));
+ module.exports = factory();
+ }
+ else if (typeof define === 'function' && define.amd)
+ {
+ // AMD. Register as an anonymous module.
+ //define(['b'], factory);
+ define('gitana', [], factory);
+ }
+ else
+ {
+ // Browser globals
+ //root.returnExports = factory(root.b);
+ root["Gitana"] = factory();
+ }
+
+}(this, function () {
+
+ //use b in some fashion.
+
+ // Just return a value to define the module export.
+ // This example returns an object, but the module
+ // can return a function as the exported value.
+ //return {};
+
+ /**
+ * This gets added into the Gitana Driver to ensure compilation time compatibility with
+ * the Appcelerator Titanium framework.
+ */
+/* jQuery Sizzle - these are to fool the Ti compiler into not reporting errors! */
+
+/**
+ * The driver assumes a globally-scoped "window" variable which is a legacy of browser-compatibility.
+ * Frameworks such as Titanium do not have a window root-scoped variable, so we fake one.
+ *
+ * At minimum, the window variable must have a setTimeout variable.
+ */
+if (typeof window === "undefined")
+{
+ window = {
+ "setTimeout": function(func, milliseconds)
+ {
+ setTimeout(func, milliseconds);
+ }
+ };
+}
+/*
+ Based on Base.js 1.1a (c) 2006-2010, Dean Edwards
+ Updated to pass JSHint and converted into a module by Kenneth Powers
+ License: http://www.opensource.org/licenses/mit-license.php
+
+ GitHub: https://github.com/KenPowers/Base.js-Module
+ */
+/*global define:true module:true*/
+/*jshint eqeqeq:true*/
+(function (name, global, definition) {
+// if (typeof module !== 'undefined') {
+// module.exports = definition();
+// } else if (typeof define !== 'undefined' && typeof define.amd === 'object') {
+// define(definition);
+// } else {
+ global[name] = definition();
+// }
+})('Base', this, function () {
+ // Base Object
+ var Base = function () {};
+
+ // Implementation
+ Base.extend = function (_instance, _static) { // subclass
+ const extend = Base.prototype.extend;
+ // build the prototype
+ Base._prototyping = true;
+ const proto = new this();
+ extend.call(proto, _instance);
+ proto.base = function () {
+ // call this method from any other method to invoke that method's ancestor
+ };
+ delete Base._prototyping;
+ // create the wrapper for the constructor function
+ //const constructor = proto.constructor.valueOf(); //-dean
+ const constructor = proto.constructor;
+ const klass = proto.constructor = function () {
+ if (!Base._prototyping) {
+ if (this._constructing || this.constructor === klass) { // instantiation
+ this._constructing = true;
+ constructor.apply(this, arguments);
+ delete this._constructing;
+ } else if (arguments[0] !== null) { // casting
+ return (arguments[0].extend || extend).call(arguments[0], proto);
+ }
+ }
+ };
+ // build the class interface
+ klass.ancestor = this;
+ klass.extend = this.extend;
+ klass.forEach = this.forEach;
+ klass.implement = this.implement;
+ klass.prototype = proto;
+ klass.toString = this.toString;
+ klass.valueOf = function (type) {
+ return (type === 'object') ? klass : constructor.valueOf();
+ };
+ extend.call(klass, _static);
+ // class initialization
+ if (typeof klass.init === 'function') klass.init();
+ return klass;
+ };
+
+ Base.prototype = {
+ extend: function (source, value) {
+ if (arguments.length > 1) { // extending with a name/value pair
+ const ancestor = this[source];
+ if (ancestor && (typeof value === 'function') && // overriding a method?
+ // the valueOf() comparison is to avoid circular references
+ (!ancestor.valueOf || ancestor.valueOf() !== value.valueOf()) && /\bbase\b/.test(value)) {
+ // get the underlying method
+ const method = value.valueOf();
+ // override
+ value = function () {
+ const previous = this.base || Base.prototype.base;
+ this.base = ancestor;
+ const returnValue = method.apply(this, arguments);
+ this.base = previous;
+ return returnValue;
+ };
+ // point to the underlying method
+ value.valueOf = function (type) {
+ return (type === 'object') ? value : method;
+ };
+ value.toString = Base.toString;
+ }
+ this[source] = value;
+ } else if (source) { // extending with an object literal
+ let extend = Base.prototype.extend;
+ // if this object has a customized extend method then use it
+ if (!Base._prototyping && typeof this !== 'function') {
+ extend = this.extend || extend;
+ }
+ const proto = {
+ toSource: null
+ };
+ // do the "toString" and other methods manually
+ const hidden = ['constructor', 'toString', 'valueOf'];
+ // if we are prototyping then include the constructor
+ for (let i = Base._prototyping ? 0 : 1; i < hidden.length; i++) {
+ const h = hidden[i];
+ if (source[h] !== proto[h])
+ extend.call(this, h, source[h]);
+ }
+ // copy each of the source object's properties to this object
+ for (let key in source) {
+ if (!proto[key]) extend.call(this, key, source[key]);
+ }
+ }
+ return this;
+ }
+ };
+
+ // initialize
+ Base = Base.extend({
+ constructor: function () {
+ this.extend(arguments[0]);
+ }
+ }, {
+ ancestor: Object,
+ version: '1.1',
+ forEach: function (object, block, context) {
+ for (let key in object) {
+ if (this.prototype[key] === undefined) {
+ block.call(context, object[key], key, object);
+ }
+ }
+ },
+ implement: function () {
+ for (let i = 0; i < arguments.length; i++) {
+ if (typeof arguments[i] === 'function') {
+ // if it's a function, call it
+ arguments[i](this.prototype);
+ } else {
+ // add the interface using the extend method
+ this.prototype.extend(arguments[i]);
+ }
+ }
+ return this;
+ },
+ toString: function () {
+ return String(this.valueOf());
+ }
+ });
+
+ // Return Base implementation
+ return Base;
+});/*
+ json2.js
+ 2012-10-08
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ const a;
+ if (typeof value === 'string') {
+ a =
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ const d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+ */
+
+/*jslint evil: true, regexp: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+ */
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (typeof JSON !== 'object') {
+ JSON = {};
+}
+
+(function () {
+ 'use strict';
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf())
+ ? this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z'
+ : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ let cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+ const c = meta[a];
+ return typeof c === 'string'
+ ? c
+ : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' : '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ let i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0
+ ? '[]'
+ : gap
+ ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
+ : '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ if (typeof rep[i] === 'string') {
+ k = rep[i];
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0
+ ? '{}'
+ : gap
+ ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
+ : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ let i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ let j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ let k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function'
+ ? walk({'': j}, '')
+ : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());(function(window)
+{
+ Gitana = Base.extend(
+ /** @lends Gitana.prototype */
+ {
+ /**
+ * @constructs
+ *
+ * @class Gitana
+ *
+ * Configuration options should look like:
+ *
+ * @param settings
+ * @param settings.clientKey: {String} the oauth2 client id,
+ * @param settings.clientSecret: {String} the oauth2 client secret,
+ * @param settings.baseURL: {String} the relative URI path of the base URL (assumed to be "/proxy"),
+ * @param settings.locale: {String} optional locale (assumed to be en_US),
+ * @param settings.storage: {String|Object} Gitana.OAuth2.Storage implementation or a string identifying where to store
+ * Gitana OAuth2 tokens ("local", "session", "memory") or empty for memory-only storage
+ * @param settings.host {String}
+ *
+ */
+ constructor: function(settings)
+ {
+ const self = this;
+
+ if (!settings)
+ {
+ settings = {};
+ }
+
+ if (settings.host)
+ {
+ settings.baseURL = settings.host + "/proxy";
+ }
+
+ this.applicationInfo = {};
+ this.stackInfo = {};
+
+ // build config
+ const config = {
+ "clientKey": null,
+ "clientSecret": null,
+ "baseURL": "/proxy",
+ "locale": (Gitana.DEFAULT_LOCALE ? Gitana.DEFAULT_LOCALE : null),
+ "application": null,
+ "loadAppHelper": true,
+ "storage": null
+ };
+ if (Gitana.DEFAULT_CONFIG)
+ {
+ for (let k in Gitana.DEFAULT_CONFIG)
+ {
+ if (Gitana.DEFAULT_CONFIG.hasOwnProperty(k))
+ {
+ config[k] = Gitana.DEFAULT_CONFIG[k];
+ }
+ }
+ }
+ Gitana.copyKeepers(config, Gitana.loadDefaultConfig());
+ Gitana.copyKeepers(config, settings);
+
+ if (typeof(config.cacheBuster) === "undefined")
+ {
+ config.cacheBuster = true;
+ }
+
+ // auto-migrate to support cloudfront
+ if (Gitana.AUTO_UPGRADE_TO_CLOUDFRONT)
+ {
+ if (config.baseURL)
+ {
+ config.baseURL = config.baseURL.replace("https://api.cloudcms.com", "https://api1.cloudcms.com");
+ config.baseURL = config.baseURL.replace("http://api.cloudcms.com", "http://api1.cloudcms.com");
+ }
+ }
+
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // APPLY CONFIGURATION SETTINGS
+ //
+
+ // baseURL
+ this.baseURL = config.baseURL;
+
+ // locale
+ this.locale = config.locale;
+
+
+
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // APPLY OAUTH2 SETTINGS
+ //
+
+ // set up our oAuth2 connection
+ const options = {};
+ if (config.clientKey) {
+ options.clientKey = config.clientKey;
+ }
+ if (config.clientSecret) {
+ options.clientSecret = config.clientSecret;
+ }
+ if (this.baseURL)
+ {
+ options.baseURL = this.baseURL;
+ options.tokenURL = "/oauth/token";
+ }
+ // the driver requires the "api" scope to be granted
+ options.requestedScope = "api";
+
+
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // PRIVILEGED METHODS
+ //
+ //
+
+ this.updateOptions = function(o)
+ {
+ if (o)
+ {
+ Gitana.copyInto(options, o);
+ }
+ };
+
+ this.resetHttp = function(config)
+ {
+ const o = {};
+ Gitana.copyInto(o, options);
+
+ if (config)
+ {
+ Gitana.copyInto(o, config);
+ }
+
+ if (!o.storage)
+ {
+ o.storage = this.getOriginalConfiguration().storage;
+ }
+
+ self.http = new Gitana.OAuth2Http(o, o.storage);
+ };
+
+ this.setAuthInfo = function(authInfo)
+ {
+ this.authInfo = authInfo;
+ };
+
+ this.setStackInfo = function(stackInfo)
+ {
+ this.stackInfo = stackInfo;
+ };
+
+ this.setApplicationInfo = function(applicationInfo)
+ {
+ this.applicationInfo = applicationInfo;
+ };
+
+ this.getOriginalConfiguration = function()
+ {
+ return config;
+ };
+
+ this.getHttpHeaders = function()
+ {
+ const self = this;
+
+ const headers = {};
+
+ if (self.http && self.http.getBearerAuthorizationHeader())
+ {
+ headers["Authorization"] = self.http.getBearerAuthorizationHeader();
+ }
+
+ return headers;
+ };
+ },
+
+ /**
+ * Sets the authentication info
+ */
+ getAuthInfo: function()
+ {
+ return this.authInfo;
+ },
+
+ getStackInfo: function()
+ {
+ return this.stackInfo;
+ },
+
+ getApplicationInfo: function()
+ {
+ return this.applicationInfo;
+ },
+
+ /**
+ * Sets the default locale for interactions with the Gitana server by this driver.
+ *
+ * @public
+ *
+ * @param {String} locale locale string
+ */
+ setLocale: function(locale)
+ {
+ this.locale = locale;
+ },
+
+ /**
+ * Retrieves the default locale being used by this driver.
+ *
+ * @returns {String} locale string
+ */
+ getLocale: function()
+ {
+ return this.locale;
+ },
+
+ /**
+ * Default AJAX failure callback
+ *
+ * @public
+ */
+ defaultFailureCallback: function(http)
+ {
+ // if we're in debug mode, log a bunch of good stuff out to console
+ if (this.debug)
+ {
+ if (typeof console != "undefined")
+ {
+ let message = "Received bad http state (" + http.status + ")";
+ let stacktrace = null;
+
+ let json = null;
+
+ const responseText = http.responseText;
+ if (responseText)
+ {
+ json = JSON.parse(responseText);
+ if (json && json.message)
+ {
+ message = message + ": " + json.message;
+ }
+ }
+
+ if (json && json["stacktrace"])
+ {
+ stacktrace = json["stacktrace"];
+ }
+
+ console.log(message);
+ if (stacktrace)
+ {
+ console.log(stacktrace);
+ }
+ }
+ }
+ },
+
+
+ /**
+ * Performs Ajax communication with the Gitana server.
+ *
+ * NOTE: For the most part, you shouldn't have to use this function since most of the things you'd want
+ * to do with the Gitana server are wrapped by helper functions.
+ *
+ * @see Gitana.Driver#gitanaGet
+ * @see Gitana.Driver#gitanaPost
+ * @see Gitana.Driver#gitanaPut
+ * @see Gitana.Driver#gitanaDel
+ * @see Gitana.Driver#gitanaRequest
+ *
+ * @public
+ *
+ * @param {String} method The kind of method to invoke - "get", "post", "put", or "del"
+ * @param {String} url The full URL to the resource being requested (i.e. "http://server:port/uri"}
+ * @param {String} [contentType] In the case of a payload carrying request (i.e. not GET), the content type being sent.
+ * @param {Object} [data] In the case of a payload carrying request (i.e. not GET), the data to plug into the payload.
+ * @param {Object} [headers] A key/value map of headers to place into the request.
+ * @param {Function} [successCallback] The function to call if the operation succeeds.
+ * @param {Function} [failureCallback] The function to call if the operation fails. If none provided, the default driver callback is used.
+ */
+ ajax: function(method, url, contentType, data, headers, successCallback, failureCallback)
+ {
+
+ // ensure headers
+ if (!headers)
+ {
+ headers = {};
+ }
+
+ // treat the method
+ if (method === null) {
+ method = "GET";
+ }
+ method = method.toUpperCase();
+
+ // flags
+ let json = false;
+ if (contentType === "application/json")
+ {
+ json = true;
+ }
+
+ // error checking
+ if ( (method === "POST" || method === "PUT") )
+ {
+ headers["Content-Type"] = contentType;
+ if (!contentType)
+ {
+ Gitana.debug("Performing method: " + method + " but missing content type");
+ return;
+ }
+ }
+
+ let toSend = data;
+
+ // special handling for json
+ if (json)
+ {
+ // if empty payload for payload-bearing methods, populate with {}
+ if (method === "PUT" || method === "POST")
+ {
+ if (!data)
+ {
+ data = {};
+ }
+ }
+
+ if (!Gitana.isString(data))
+ {
+ // stringify
+ toSend = Gitana.stringify(data);
+ }
+ }
+
+ //
+ // if the URL is relative and we're running in a browser, then we can pad the URL
+ // based on the URL of the browser
+ //
+ // otherwise, we can't handle relative URLs
+ //
+ if (url.substring(0,1) === "/")
+ {
+ // if window.location exists, then we're running on a browser
+ if (!Gitana.isUndefined(window.location))
+ {
+ let u = window.location.protocol + "//" + window.location.host;
+ if (window.location.host.indexOf(":") === -1)
+ {
+ if (window.location.port) {
+ u += ":" + window.location.port;
+ }
+ }
+ url = u + url;
+ }
+ else
+ {
+ // relative urls are not supported outside of the browser
+ throw new Error("Relative URL not supported outside of the browser: " + url);
+ }
+ }
+
+ const config = {
+ "method": method,
+ "url": url,
+ "data": toSend,
+ "headers": headers,
+ "success": successCallback,
+ "failure": failureCallback
+ };
+
+ Gitana.requestCount++;
+ this.http.request(config);
+
+ return this;
+ },
+
+ /**
+ * Send an HTTP request via AJAX to the Gitana Server.
+ *
+ * This method will additionally make sure of the following:
+ *
+ * 1) That the Gitana Driver authentication ticket is plugged onto the request.
+ * 2) That the Gitana Driver locale is plugged onto the request.
+ * 3) That full object data is returned (including metadata).
+ *
+ * @public
+ *
+ * @param {String} method The kind of method to invoke - "get", "post", "put", or "del"
+ * @param {String} url Either a full URL (i.e. "http://server:port/uri") or a URI against the driver's server URL (i.e. /repositories/...)
+ * @param {Object} params parameter map
+ * @param {String} contentType If the case of a payload carrying request (i.e. not GET), the content type being sent.
+ * @param {Object} data In the case of a payload carrying request (i.e. not GET), the JSON to plug into the payload.
+ * @param headers
+ * @param {Function} [successCallback] The function to call if the operation succeeds.
+ * @param {Function} [failureCallback] The function to call if the operation fails.
+ */
+ gitanaRequest: function(method, url, params, contentType, data, headers, successCallback, failureCallback)
+ {
+ // ensure we have some params
+ if (!params)
+ {
+ params = {};
+ }
+
+ // if url has query string params, move into params
+ // strip back url so that it does not have query params
+ const x1 = url.indexOf("?");
+ if (x1 > -1)
+ {
+ const qs = url.substring(x1 + 1);
+ url = url.substring(0, x1);
+
+ const parts = qs.split("&");
+ for (let x2 = 0; x2 < parts.length; x2++)
+ {
+ const keyValuePair = parts[x2].split("=");
+ params[keyValuePair[0]] = keyValuePair[1];
+ }
+ }
+
+ // make sure we compute the real url
+ if (Gitana.startsWith(url, "/")) {
+ url = this.baseURL + url;
+ }
+
+ if (!failureCallback)
+ {
+ failureCallback = this.defaultFailureCallback;
+ }
+
+ if (!headers)
+ {
+ headers = {};
+ }
+
+ /**
+ * Primary success callback handler for oAuth call to server.
+ *
+ * @param responseObject
+ * @param xhr
+ */
+ const onSuccess = function(responseObject, xhr)
+ {
+ if (successCallback)
+ {
+ // call back with just the response text (or json)
+
+ let arg = responseObject.text;
+ if (contentType === "application/json")
+ {
+ try {
+ arg = new Gitana.Response(JSON.parse(arg));
+ } catch (ex) {
+ failureCallback(ex);
+ }
+ }
+
+ successCallback(arg);
+ }
+ };
+
+ /**
+ * Primary failure callback handler for oAuth call to server.
+ *
+ * @param responseObject
+ * @param xhr
+ */
+ const onFailure = function(responseObject, xhr)
+ {
+ if (failureCallback)
+ {
+ const httpError = {};
+
+ if (responseObject && responseObject.timeout)
+ {
+ // due to a timeout
+ httpError["statusText"] = "Connection timed out";
+ httpError["status"] = xhr.status;
+ httpError["errorType"] = "timeout";
+ httpError["message"] = "Connection timed out";
+ httpError["xhr"] = xhr;
+ httpError["response"] = responseObject;
+
+ if (responseObject.info)
+ {
+ httpError["info"] = responseObject.info;
+ }
+ }
+ else
+ {
+ // due to an HTTP error
+ httpError["statusText"] = xhr.statusText;
+ httpError["status"] = xhr.status;
+ httpError["errorType"] = "http";
+ httpError["xhr"] = xhr;
+
+ if (responseObject)
+ {
+ httpError["response"] = responseObject;
+ }
+
+ let message = null;
+ let stacktrace = null;
+
+ if (contentType === "application/json")
+ {
+ try
+ {
+ const arg = responseObject.text;
+
+ const obj = new Gitana.Response(JSON.parse(arg));
+ if (obj.message)
+ {
+ message = obj.message;
+ }
+ if (obj.stacktrace)
+ {
+ stacktrace = obj.stacktrace;
+ }
+ }
+ catch (e) { }
+ }
+ if (message)
+ {
+ httpError.message = message;
+ }
+ if (stacktrace)
+ {
+ httpError.stacktrace = stacktrace;
+ }
+ }
+
+ failureCallback(httpError);
+ }
+ };
+
+ // copy in globally defined params
+ if (Gitana.HTTP_PARAMS)
+ {
+ for (let k in Gitana.HTTP_PARAMS)
+ {
+ if (Gitana.HTTP_PARAMS.hasOwnProperty(k))
+ {
+ params[k] = Gitana.HTTP_PARAMS[k];
+ }
+ }
+ }
+
+ // copy in globally defined headers
+ if (Gitana.HTTP_HEADERS)
+ {
+ for (let k in Gitana.HTTP_HEADERS)
+ {
+ if (Gitana.HTTP_HEADERS.hasOwnProperty(k))
+ {
+ headers[k] = Gitana.HTTP_HEADERS[k];
+ }
+ }
+ }
+
+ // adjust url to include "full" as well as "metadata" if not included
+ if (Gitana.isEmpty(params["metadata"]))
+ {
+ params["metadata"] = true;
+ }
+ if (Gitana.isEmpty(params["full"]))
+ {
+ params["full"] = true;
+ }
+
+ // set the locale
+ if (this.locale === null)
+ {
+ if (!params["locale"])
+ {
+ params["locale"] = "default";
+ }
+ }
+ else if (typeof(this.locale) !== "undefined")
+ {
+ headers["accept-language"] = this.locale;
+ params["locale"] = this.locale;
+ }
+
+ // cache buster
+ let cacheBuster = null;
+ if (this.getOriginalConfiguration().cacheBuster === true)
+ {
+ cacheBuster = new Date().getTime();
+ }
+ else if (typeof(this.getOriginalConfiguration().cacheBuster) === "string")
+ {
+ cacheBuster = this.getOriginalConfiguration().cacheBuster;
+ }
+ else if (typeof(this.getOriginalConfiguration().cacheBuster) === "function")
+ {
+ cacheBuster = this.getOriginalConfiguration().cacheBuster();
+ }
+ if (cacheBuster)
+ {
+ params["cb"] = cacheBuster;
+ }
+
+ // update URL to include params
+ for (let paramKey in params)
+ {
+ let paramValue = params[paramKey];
+ if (Gitana.isFunction(paramValue))
+ {
+ paramValue = paramValue.call();
+ }
+ else if (Gitana.isString(paramValue))
+ {
+ // make sure all param strings are escaped
+ paramValue = Gitana.escape(paramValue);
+ }
+ else if (Gitana.isNumber(paramValue))
+ {
+ // NOTHING TO DO
+ }
+ else
+ {
+ paramValue = Gitana.escape(Gitana.stringify(paramValue, false));
+ }
+
+ // apply
+ if (url.indexOf("?") > -1)
+ {
+ url = url + "&" + paramKey + "=" + paramValue;
+ }
+ else
+ {
+ url = url + "?" + paramKey + "=" + paramValue;
+ }
+ }
+
+ return this.ajax(method, url, contentType, data, headers, onSuccess, onFailure);
+ },
+
+ /**
+ * Sends an HTTP GET request to the Gitana server.
+ *
+ * @public
+ *
+ * @param {String} url Either a full URL (i.e. "http://server:port/uri") or a URI against the driver's server URL (i.e. /repositories/...)
+ * @param {Object} params request parameters
+ * @param {Object} headers request headers
+ * @param {Function} [successCallback] The function to call if the operation succeeds.
+ * @param {Function} [failureCallback] The function to call if the operation fails.
+ */
+ gitanaGet: function(url, params, headers, successCallback, failureCallback)
+ {
+ return this.gitanaRequest("GET", url, params, "application/json", null, headers, successCallback, failureCallback);
+ },
+
+ /**
+ * Sends an HTTP GET request to the Gitana server.
+ *
+ * @public
+ *
+ * @param {String} url Either a full URL (i.e. "http://server:port/uri") or a URI against the driver's server URL (i.e. /repositories/...)
+ * @param {Object} params request parameters
+ * @param {Function} [successCallback] The function to call if the operation succeeds.
+ * @param {Function} [failureCallback] The function to call if the operation fails.
+ */
+ gitanaDownload: function(url, params, successCallback, failureCallback)
+ {
+ return this.gitanaRequest("GET", url, params, null, null, {}, successCallback, failureCallback);
+ },
+
+ /**
+ * Sends an HTTP POST request to the Gitana server.
+ *
+ * @public
+ *
+ * @param {String} url Either a full URL (i.e. "http://server:port/uri") or a URI against the driver's server URL (i.e. /repositories/...)
+ * @param {Object} params request parameters
+ * @param {Object} [jsonData] The JSON to plug into the payload.
+ * @param {Function} [successCallback] The function to call if the operation succeeds.
+ * @param {Function} [failureCallback] The function to call if the operation fails.
+ */
+ gitanaPost: function(url, params, jsonData, successCallback, failureCallback)
+ {
+ return this.gitanaRequest("POST", url, params, "application/json", jsonData, {}, successCallback, failureCallback);
+ },
+
+ /**
+ * Sends an HTTP POST request to the Gitana server.
+ *
+ * @public
+ *
+ * @param {String} url Either a full URL (i.e. "http://server:port/uri") or a URI against the driver's server URL (i.e. /repositories/...)
+ * @param {Object} params request parameters
+ * @param {String} contentType content type being sent
+ * @param {Object} data The JSON to plug into the payload.
+ * @param {Function} [successCallback] The function to call if the operation succeeds.
+ * @param {Function} [failureCallback] The function to call if the operation fails.
+ */
+ gitanaUpload: function(url, params, contentType, data, successCallback, failureCallback)
+ {
+ return this.gitanaRequest("POST", url, params, contentType, data, {}, successCallback, failureCallback);
+ },
+
+ /**
+ * Sends an HTTP PUT request to the Gitana server.
+ *
+ * @public
+ *
+ * @param {String} url Either a full URL (i.e. "http://server:port/uri") or a URI against the driver's server URL (i.e. /repositories/...)
+ * @param {Object} params request parameters
+ * @param {Object} [jsonData] The JSON to plug into the payload.
+ * @param {Function} [successCallback] The function to call if the operation succeeds.
+ * @param {Function} [failureCallback] The function to call if the operation fails.
+ */
+ gitanaPut: function(url, params, jsonData, successCallback, failureCallback)
+ {
+ return this.gitanaRequest("PUT", url, params, "application/json", jsonData, {}, successCallback, failureCallback);
+ },
+
+ /**
+ * Sends an HTTP PATCH request to the Gitana server.
+ *
+ * @public
+ *
+ * @param {String} url Either a full URL (i.e. "http://server:port/uri") or a URI against the driver's server URL (i.e. /repositories/...)
+ * @param {Object} params request parameters
+ * @param {Object} [jsonData] The JSON to plug into the payload.
+ * @param {Function} [successCallback] The function to call if the operation succeeds.
+ * @param {Function} [failureCallback] The function to call if the operation fails.
+ */
+ gitanaPatch: function(url, params, jsonData, successCallback, failureCallback)
+ {
+ return this.gitanaRequest("PATCH", url, params, "application/json", jsonData, {}, successCallback, failureCallback);
+ },
+
+ /**
+ * Sends an HTTP DELETE request to the Gitana server.
+ *
+ * @public
+ *
+ * @param {String} url Either a full URL (i.e. "http://server:port/uri") or a URI against the driver's server URL (i.e. /repositories/...)
+ * @param {Object} params request parameters
+ * @param {Function} [successCallback] The function to call if the operation succeeds.
+ * @param {Function} [failureCallback] The function to call if the operation fails.
+ */
+ gitanaDelete: function(url, params, successCallback, failureCallback)
+ {
+ return this.gitanaRequest("DELETE", url, params, "application/json", null, {}, successCallback, failureCallback);
+ },
+
+ getFactory: function()
+ {
+ return new Gitana.ObjectFactory();
+ },
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CHAINING METHODS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Authenticates as the supplied user.
+ *
+ * A user can either be authenticated using username/password credentials or via an authentication code.
+ *
+ * Authorization Code flow:
+ *
+ * {
+ * "code": "",
+ * "redirectUri": ""
+ * }
+
+ * Username/password flow:
+ *
+ * {
+ * "username": "",
+ * "password": ""
+ * }
+ *
+ * Implicit flow:
+ *
+ * {
+ * "accessToken": "",
+ * "redirectUri": ""
+ * }
+ *
+ * Using Gitana Ticket from a cookie:
+ *
+ * {
+ * "cookie": true
+ * }
+ *
+ * Using Gitana Ticket (explicitly provided):
+ *
+ * {
+ * "ticket": ""
+ * }
+ *
+ * An authentication failure handler can be passed as the final argument
+ *
+ * @chained platform
+ *
+ * @param {Object} settings
+ * @param {Function} authFailureHandler failure handler
+ */
+ authenticate: function(settings, authFailureHandler)
+ {
+ const driver = this;
+
+ // build config
+ const config = {
+ "code": null,
+ "redirectUri": null,
+ "username": null,
+ "password": null,
+ "accessToken": null,
+ "ticket": null,
+ "cookie": null,
+ "ticketMaxAge": null,
+ "headers": {}
+ };
+ Gitana.copyKeepers(config, Gitana.loadDefaultConfig());
+ Gitana.copyKeepers(config, settings);
+
+ // some adjustments
+ if (config.ticket || config.accessToken || config.code)
+ {
+ delete config.username;
+ delete config.password;
+
+ if (config.ticket)
+ {
+ delete config.accessToken;
+ delete config.code;
+ }
+ else if (config.accessToken)
+ {
+ delete config.ticket;
+ delete config.code;
+ }
+ else if (config.code)
+ {
+ delete config.accessToken;
+ delete config.ticket;
+ }
+ }
+
+ // platform config (for cache key determination)
+ const platformConfig = {
+ "key": null,
+ "ticket": null,
+ "username": null,
+ "clientKey": null
+ };
+ Gitana.copyKeepers(platformConfig, this.getOriginalConfiguration());
+ Gitana.copyKeepers(platformConfig, settings);
+ let platformCacheKey = platformConfig.key;
+ if (!platformCacheKey)
+ {
+ platformCacheKey = Gitana.determinePlatformCacheKey(platformConfig, true);
+ }
+ if (platformCacheKey)
+ {
+ this.platformCacheKey = platformCacheKey;
+ }
+
+ const params = {};
+ const headers = {};
+
+ // copy in any custom headers
+ if (config.headers) {
+ for (const header in config.headers) {
+ headers[header] = config.headers[header];
+ }
+ }
+
+ // build a cluster instance
+ const cluster = new Gitana.Cluster(this, {});
+
+ const applyPlatformCache = function(driver, platform)
+ {
+ const platformCacheKey = driver.platformCacheKey;
+ if (platformCacheKey)
+ {
+ Gitana.PLATFORM_CACHE(platformCacheKey, platform);
+ }
+
+ // always cache on ticket as well
+ const ticket = driver.getAuthInfo().getTicket();
+ if (ticket) {
+ Gitana.PLATFORM_CACHE(ticket, platform);
+ }
+ };
+
+ // run with this = platform
+ const doAuthenticate = function()
+ {
+ const platform = this;
+
+ // we provide a fallback if no flow type is specified, using "password" flow with guest/guest
+ if (!config.code && !config.username && !config.accessToken && !config.cookie && !config.ticket)
+ {
+ config.username = "guest";
+ config.password = "guest";
+ }
+
+ //
+ // authenticate via the authentication flow
+ //
+ if (config.code)
+ {
+ // clear existing cookie and ticket
+ config.authorizationFlow = Gitana.OAuth2Http.AUTHORIZATION_CODE;
+ driver.resetHttp(config);
+ Gitana.deleteCookie("GITANA_TICKET", "/");
+
+ // fetch the auth info
+ driver.gitanaGet("/auth/info", params, headers, function(response) {
+
+ const authInfo = new Gitana.AuthInfo(response);
+ driver.setAuthInfo(authInfo);
+
+ // TODO: fix this
+ // kill the JSESSIONID cookie which comes back from the proxy and ties us to a session
+ // on the Gitana server
+ Gitana.deleteCookie("JSESSIONID", "/");
+
+ // apply platform cache
+ applyPlatformCache(driver, platform);
+
+ // now continue the platform chain after we reload
+ platform.reload();
+ platform.next();
+
+ }, function(http) {
+
+ // if authentication fails, respond to custom auth failure handler
+ if (authFailureHandler)
+ {
+ authFailureHandler.call(platform, http);
+ }
+
+ });
+ }
+
+ //
+ // authenticate via password flow
+ //
+ else if (config.username)
+ {
+ // clear existing cookie and ticket
+ config.authorizationFlow = Gitana.OAuth2Http.PASSWORD;
+ driver.resetHttp(config);
+ Gitana.deleteCookie("GITANA_TICKET", "/");
+
+ // retrieve auth info and plug into the driver
+ driver.gitanaGet("/auth/info", params, headers, function(response) {
+ const authInfo = new Gitana.AuthInfo(response);
+ driver.setAuthInfo(authInfo);
+
+ // TODO: fix this
+ // kill the JSESSIONID cookie which comes back from the proxy and ties us to a session
+ // on the Gitana server
+ Gitana.deleteCookie("JSESSIONID", "/");
+
+ // apply platform cache
+ applyPlatformCache(driver, platform);
+
+ // now continue the platform chain after we reload
+ platform.reload();
+ platform.next();
+
+ }, function(http) {
+
+ // if authentication fails, respond to custom auth failure handler
+ if (authFailureHandler)
+ {
+ authFailureHandler.call(platform, http);
+ }
+
+ });
+ }
+
+ //
+ // authenticate with an existing token
+ //
+ else if (config.accessToken)
+ {
+ // clear existing cookie and ticket
+ config.authorizationFlow = Gitana.OAuth2Http.TOKEN;
+ driver.resetHttp(config);
+ Gitana.deleteCookie("GITANA_TICKET", "/");
+
+ // fetch the auth info
+ driver.gitanaGet("/auth/info", params, headers, function(response) {
+
+ const authInfo = new Gitana.AuthInfo(response);
+ driver.setAuthInfo(authInfo);
+
+ // TODO: fix this
+ // kill the JSESSIONID cookie which comes back from the proxy and ties us to a session
+ // on the Gitana server
+ Gitana.deleteCookie("JSESSIONID", "/");
+
+ // apply platform cache
+ applyPlatformCache(driver, platform);
+
+ // now continue the platform chain after we reload
+ platform.reload();
+ platform.next();
+
+ }, function(http) {
+
+ // if authentication fails, respond to custom auth failure handler
+ if (authFailureHandler)
+ {
+ authFailureHandler.call(platform, http);
+ }
+
+ });
+ }
+
+ //
+ // authenticate using an existing cookie
+ //
+ else if (config.cookie)
+ {
+ // reuse an existing cookie (token flow)
+ config.authorizationFlow = Gitana.OAuth2Http.COOKIE;
+ driver.resetHttp(config);
+
+ // fetch the auth info
+ driver.gitanaGet("/auth/info", params, headers, function(response) {
+
+ const authInfo = new Gitana.AuthInfo(response);
+ driver.setAuthInfo(authInfo);
+
+ if (authInfo.accessToken)
+ {
+ driver.http.accessToken(authInfo.accessToken);
+ }
+
+ // TODO: fix this
+ // kill the JSESSIONID cookie which comes back from the proxy and ties us to a session
+ // on the Gitana server
+ Gitana.deleteCookie("JSESSIONID", "/");
+
+ // apply platform cache
+ applyPlatformCache(driver, platform);
+
+ // now continue the platform chain after we reload
+ platform.reload();
+ platform.next();
+
+ }, function(http) {
+
+ // if authentication fails, respond to custom auth failure handler
+ if (authFailureHandler)
+ {
+ authFailureHandler.call(platform, http);
+ }
+
+ });
+ }
+
+ //
+ // authenticate using an explicit gitana ticket
+ //
+ else if (config.ticket)
+ {
+ // reuse an existing cookie (token flow)
+ config.authorizationFlow = Gitana.OAuth2Http.TICKET;
+ driver.resetHttp(config);
+
+ headers["GITANA_TICKET"] = config.ticket;
+
+ // fetch the auth info
+ driver.gitanaGet("/auth/info", params, headers, function(response) {
+
+ const authInfo = new Gitana.AuthInfo(response);
+ driver.setAuthInfo(authInfo);
+
+ // TODO: fix this
+ // kill the JSESSIONID cookie which comes back from the proxy and ties us to a session
+ // on the Gitana server
+ Gitana.deleteCookie("JSESSIONID", "/");
+
+ // apply platform cache
+ applyPlatformCache(driver, platform);
+
+ // now continue the platform chain after we reload
+ platform.reload();
+ platform.next();
+
+ }, function(http) {
+
+ // if authentication fails, respond to custom auth failure handler
+ if (authFailureHandler)
+ {
+ authFailureHandler.call(platform, http);
+ }
+
+ });
+ }
+ else
+ {
+ const message = "Unsupported authentication flow - you must provide either a username, authorization code, access token or select cookie-based authentication";
+
+ if (authFailureHandler)
+ {
+ authFailureHandler.call(platform, {
+ "message": message
+ });
+ }
+ else
+ {
+ throw new Error(message);
+ }
+ }
+ };
+
+ const result = this.getFactory().platform(cluster);
+ return Chain(result).then(function() {
+
+ // NOTE: this = platform
+
+ doAuthenticate.call(this);
+
+ // tell the chain that we'll manually handle calling next()
+ return false;
+ });
+ },
+
+ reloadAuthInfo: function(callback)
+ {
+ const driver = this;
+
+ driver.gitanaGet("/auth/info", {}, {}, function(response) {
+
+ const authInfo = new Gitana.AuthInfo(response);
+ driver.setAuthInfo(authInfo);
+
+ callback();
+
+ }, function(http) {
+ callback(null, http);
+ });
+ },
+
+ /**
+ * Clears any authentication for the driver.
+ */
+ clearAuthentication: function()
+ {
+ if (this.http.clearStorage)
+ {
+ this.http.clearStorage();
+ }
+
+ this.resetHttp();
+ Gitana.deleteCookie("GITANA_TICKET", "/");
+ },
+
+ /**
+ * Refreshes the authentication access token.
+ *
+ * @param callback
+ */
+ refreshAuthentication: function(callback)
+ {
+ this.http.refresh(function(err) {
+ callback(err);
+ });
+ },
+
+ /**
+ * Destructor function, called at the end of the driver instance's lifecycle
+ */
+ destroy: function()
+ {
+ this.clearAuthentication();
+ }
+
+ });
+
+
+ //
+ // STATICS
+ // Special Groups
+
+ Gitana.EVERYONE = {
+ "name": "everyone",
+ "type": "GROUP"
+ };
+
+ // temporary location for this code
+ Gitana.toCopyDependencyChain = function(typedID)
+ {
+ let array = [];
+
+ if (typedID.getType() === "node")
+ {
+ array = array.concat(Gitana.toCopyDependencyChain(typedID.getBranch()));
+ array = array.concat({
+ "typeId": "changeset",
+ "id": typedID.getSystemMetadata().getChangesetId()
+ });
+ }
+ else if (typedID.getType() === "association")
+ {
+ array = array.concat(Gitana.toCopyDependencyChain(typedID.getBranch()));
+ array = array.concat({
+ "typeId": "changeset",
+ "id": typedID.getSystemMetadata().getChangesetId()
+ });
+ }
+ else if (typedID.getType() === "branch")
+ {
+ array = array.concat(Gitana.toCopyDependencyChain(typedID.getRepository()));
+ }
+ else if (typedID.getType() === "platform")
+ {
+ // nothing to do here
+ }
+ else if (typedID.getType() === "stack")
+ {
+ array = array.concat(Gitana.toCopyDependencyChain(typedID.getPlatform()));
+ }
+ else if (typedID.getType() === "project")
+ {
+ array = array.concat(Gitana.toCopyDependencyChain(typedID.getPlatform()));
+ }
+ else
+ {
+ array = array.concat(Gitana.toCopyDependencyChain(typedID.getPlatform()));
+ }
+
+ array.push(Gitana.toDependencyObject(typedID));
+
+ return array;
+ };
+
+ Gitana.toDependencyObject = function(typedID)
+ {
+ return {
+ "typeId": typedID.getType(),
+ "id": typedID.getId()
+ };
+ };
+
+ Gitana.TypedIDConstants = {};
+ Gitana.TypedIDConstants.TYPE_APPLICATION = "application";
+ Gitana.TypedIDConstants.TYPE_EMAIL = "email";
+ Gitana.TypedIDConstants.TYPE_EMAIL_PROVIDER = "emailprovider";
+ Gitana.TypedIDConstants.TYPE_REGISTRATION = "registration";
+ Gitana.TypedIDConstants.TYPE_PAGE_RENDITION = "pageRendition";
+ Gitana.TypedIDConstants.TYPE_SETTINGS = "settings";
+ Gitana.TypedIDConstants.TYPE_MESSAGE = "message";
+
+ // cluster
+ Gitana.TypedIDConstants.TYPE_CLUSTER = "cluster";
+ Gitana.TypedIDConstants.TYPE_JOB = "job";
+ Gitana.TypedIDConstants.TYPE_LOG_ENTRY = "logEntry";
+
+ // directory
+ Gitana.TypedIDConstants.TYPE_DIRECTORY = "directory";
+ Gitana.TypedIDConstants.TYPE_IDENTITY = "identity";
+ Gitana.TypedIDConstants.TYPE_CONNECTION = "connection";
+
+ // domain
+ Gitana.TypedIDConstants.TYPE_DOMAIN = "domain";
+ Gitana.TypedIDConstants.TYPE_DOMAIN_GROUP = "group";
+ Gitana.TypedIDConstants.TYPE_DOMAIN_USER = "user";
+
+ // platform
+ Gitana.TypedIDConstants.TYPE_PLATFORM = "platform";
+ Gitana.TypedIDConstants.TYPE_ACCESS_POLICY = "accessPolicy";
+ Gitana.TypedIDConstants.TYPE_AUTHENTICATION_GRANT = "authenticationGrant";
+ Gitana.TypedIDConstants.TYPE_BILLING_PROVIDERS_CONFIGURATION = "billingProviderConfiguration";
+ Gitana.TypedIDConstants.TYPE_DEPLOYMENT_RECEIVER = "deployment-receiver";
+ Gitana.TypedIDConstants.TYPE_DEPLOYMENT_PACKAGE = "deployment-package";
+ Gitana.TypedIDConstants.TYPE_DEPLOYMENT_STRATEGY = "deployment-strategy";
+ Gitana.TypedIDConstants.TYPE_DEPLOYMENT_TARGET = "deployment-target";
+ Gitana.TypedIDConstants.TYPE_CLIENT = "client";
+ Gitana.TypedIDConstants.TYPE_DESCRIPTOR = "externalServiceDescriptor";
+ Gitana.TypedIDConstants.TYPE_STACK = "stack";
+ Gitana.TypedIDConstants.TYPE_PROJECT = "project";
+ Gitana.TypedIDConstants.TYPE_SCHEDULED_WORK = "scheduled-work";
+ Gitana.TypedIDConstants.TYPE_REPORT = "report";
+ Gitana.TypedIDConstants.TYPE_WORKFLOW_INSTANCE = "workflowInstance";
+ Gitana.TypedIDConstants.TYPE_WORKFLOW_MODEL = "workflowModel";
+ Gitana.TypedIDConstants.TYPE_WORKFLOW_TASK = "workflowTask";
+ Gitana.TypedIDConstants.TYPE_WORKFLOW_COMMENT = "workflowComment";
+ Gitana.TypedIDConstants.TYPE_UICONFIG = "uiconfig";
+
+ // registrar
+ Gitana.TypedIDConstants.TYPE_REGISTRAR = "registrar";
+ Gitana.TypedIDConstants.TYPE_METER = "meter";
+ Gitana.TypedIDConstants.TYPE_PLAN = "plan";
+ Gitana.TypedIDConstants.TYPE_TENANT = "tenant";
+
+ // repository
+ Gitana.TypedIDConstants.TYPE_REPOSITORY = "repository";
+ Gitana.TypedIDConstants.TYPE_ASSOCIATION = "association";
+ Gitana.TypedIDConstants.TYPE_BRANCH = "branch";
+ Gitana.TypedIDConstants.TYPE_CHANGESET = "changeset";
+ Gitana.TypedIDConstants.TYPE_NODE = "node";
+ Gitana.TypedIDConstants.TYPE_RELEASE = "release";
+ Gitana.TypedIDConstants.TYPE_MERGE_CONFLICT = "mergeConflict";
+ Gitana.TypedIDConstants.TYPE_DELETION = "deletion";
+
+ // vault
+ Gitana.TypedIDConstants.TYPE_VAULT = "vault";
+ Gitana.TypedIDConstants.TYPE_ARCHIVE = "archive";
+
+ // web host
+ Gitana.TypedIDConstants.TYPE_WEB_HOST = "webhost";
+ Gitana.TypedIDConstants.TYPE_AUTO_CLIENT_MAPPING = "autoClientMapping";
+ Gitana.TypedIDConstants.TYPE_TRUSTED_DOMAIN_MAPPING = "trustedDomainMapping";
+ Gitana.TypedIDConstants.TYPE_DEPLOYED_APPLICATION = "deployedApplication";
+
+ Gitana.handleJobCompletion = function(chain, cluster, jobId, synchronous, reportFn)
+ {
+ const jobFinalizer = function() {
+
+ return Chain(cluster).readJob(jobId).then(function() {
+
+ if (reportFn) {
+ reportFn(this);
+ }
+
+ if (!synchronous || (synchronous && (this.getState() === "FINISHED" || this.getState() === "ERROR")))
+ {
+ chain.loadFrom(this);
+ chain.next();
+ }
+ else
+ {
+ // reset timeout
+ window.setTimeout(jobFinalizer, 1000);
+ }
+
+ });
+ };
+
+ // set timeout
+ window.setTimeout(jobFinalizer, 1000);
+ };
+
+ /** Extension point for loading default config for server-side containers **/
+ Gitana.loadDefaultConfig = function()
+ {
+ };
+
+ /**
+ * Simple in-memory cache implementation for use by-default by the driver.
+ *
+ * @return {Function}
+ */
+ Gitana.MemoryCache = function()
+ {
+ const cache = {};
+
+ return function(k, v)
+ {
+ if (!Gitana.isUndefined(v))
+ {
+ if (v) {
+ cache[k] = v;
+ }
+ else {
+ delete cache[k];
+ }
+ }
+
+ // support for "clear" method - removes everything from cache
+ if (k === "clear")
+ {
+ const za = [];
+ for (let z in cache)
+ {
+ za.push(z);
+ }
+ for (let i = 0; i < za.length; i++)
+ {
+ delete cache[za[i]];
+ }
+ }
+
+ return cache[k];
+ };
+ };
+
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // PLATFORM CACHE
+ //
+ //
+
+ // extension point - override with other implementations
+ Gitana.PLATFORM_CACHE = Gitana.MemoryCache();
+
+ Gitana.determinePlatformCacheKey = function(config, fallbackToDefault)
+ {
+ let cacheKey = null;
+
+ // "ticket" authentication - key = ticket
+ if (config.ticket) {
+ cacheKey = config.ticket;
+ }
+ else if (config.clientKey && config.username) {
+ cacheKey = config.clientKey + ":" + config.username;
+ }
+ else if (fallbackToDefault)
+ {
+ // if no config provided, use "default" key
+ cacheKey = "default";
+ }
+
+ return cacheKey;
+ };
+
+ /**
+ * Connects to a Gitana platform.
+ *
+ * @param config
+ * @param callback {Object} optional callback function that gets called once the server has been connected to. If no
+ * "application" config parameter is present, then the callback function is called with the this
+ * context set to the platform. If an "application" config parameter is present, then the stack
+ * for the application is loaded and references are resolved and the this context will be the
+ * app helper instance. This callback also acts as an error handler for any authentication issues.
+ * If an auth error happens, the err is passed to the callback as the first and only argument.
+ *
+ * @return {*}
+ */
+ Gitana.connect = function(config, callback)
+ {
+ // allow for no config, callback-only
+ if (Gitana.isFunction(config)) {
+ callback = config;
+ config = null;
+ }
+
+ let missingConfig = false;
+
+ if (!config) {
+ config = {};
+ missingConfig = true;
+ }
+
+ if (Gitana.isString(config)) {
+ config = {"key": config};
+ }
+
+ // by default, set invalidatePlatformCache to false
+ if (typeof(config.invalidatePlatformCache) === "undefined")
+ {
+ config.invalidatePlatformCache = false;
+ }
+
+ // if no config key specified, we can generate one...
+ if (!config.key)
+ {
+ config.key = Gitana.determinePlatformCacheKey(config, missingConfig);
+ }
+
+ // default to load app helper if not defined
+ if (typeof(config.loadAppHelper) === "undefined")
+ {
+ config.loadAppHelper = true;
+ }
+
+ // this gets called once the platform is drawn from cache or created
+ // fires the callback and passes in the platform or the app helper
+ const setupContext = function(platformCacheKey)
+ {
+ // NOTE: this == platform
+
+ // if their configuration contains the "application" setting, then auto-load the app() context
+ // note that config.application could be undefined (we require explicit NULL here for copyKeepers)
+ if (config.loadAppHelper)
+ {
+ const appConfig = {
+ "application": (config.application ? config.application: null),
+ "appCacheKey": null
+ };
+ Gitana.copyKeepers(appConfig, Gitana.loadDefaultConfig());
+ Gitana.copyKeepers(appConfig, this.getDriver().getOriginalConfiguration());
+ Gitana.copyKeepers(appConfig, config);
+ if (appConfig.application) {
+
+ const appSettings = {
+ "application": appConfig.application
+ };
+ if (appConfig.appCacheKey) {
+ appSettings.appCacheKey = appConfig.appCacheKey;
+ }
+ if (!appSettings.appCacheKey)
+ {
+ if (platformCacheKey)
+ {
+ appSettings.appCacheKey = platformCacheKey + "_" + appConfig.application;
+ }
+ }
+ this.app(appSettings, function(err) {
+ if (callback) {
+ // NOTE: this == app helper
+ callback.call(this, err);
+ }
+ });
+ }
+ else {
+ if (callback) {
+ callback.call(this);
+ }
+ }
+ }
+ else
+ {
+ if (callback) {
+ callback.call(this);
+ }
+ }
+ };
+
+ // support for invalidatePlatformCache
+ if (config.key && config.invalidatePlatformCache)
+ {
+ Gitana.disconnect(config.key);
+ }
+
+ // either retrieve platform from cache or authenticate
+ let platform = null;
+ if (config.key) {
+ platform = Gitana.PLATFORM_CACHE(config.key);
+ }
+ if (platform)
+ {
+ // platform already loaded
+
+ // spawn off a new copy for thread safety
+ platform = Chain(new Gitana.Platform(platform.getCluster(), platform));
+ setupContext.call(platform, config.key);
+ return platform;
+ }
+
+ // if they didn't provide a config and made it this far, then lets assume a cookie based config?
+ if (missingConfig)
+ {
+ config["cookie"] = true;
+ }
+
+ // load it up
+ return new Gitana(config).authenticate(config, function(err) {
+
+ // if we failed to authenticate, force disconnect in case we were pulling from cache
+ if (err && config.key) {
+ Gitana.disconnect(config.key);
+ }
+
+ if (callback) {
+ callback.call(this, err);
+ }
+
+ }).then(function() {
+
+ // NOTE: this == platform
+
+ setupContext.call(this, config.key);
+
+ });
+ };
+
+ /**
+ * Disconnects a platform from the cache.
+ *
+ * @param key
+ * @param expireAccessToken
+ */
+ Gitana.disconnect = function(key, expireAccessToken)
+ {
+ if (!key) {
+ key = "default";
+ }
+
+ const platform = Gitana.PLATFORM_CACHE(key);
+ if (platform)
+ {
+ // if we are meant to expire the server-side access token,
+ // fire off a signal to the Cloud CMS server to do so
+ // we ignore whether this succeeds or fails
+ if (expireAccessToken)
+ {
+ platform.getDriver().gitanaPost("/auth/expire", {}, {}, function() {
+ // success
+ }, function(err) {
+ // error
+ });
+ }
+
+ const badKeys = [];
+ for (let k in Gitana.APPS)
+ {
+ if (k.indexOf(key + "_") === 0)
+ {
+ badKeys.push(k);
+ }
+ }
+ for (let i = 0; i < badKeys.length; i++)
+ {
+ delete Gitana.APPS[badKeys[i]];
+ }
+
+ const ticket = platform.getDriver().getAuthInfo().getTicket();
+ if (ticket)
+ {
+ Gitana.PLATFORM_CACHE(ticket, null);
+ }
+
+ Gitana.PLATFORM_CACHE(key, null);
+
+ platform.getDriver().destroy();
+ }
+ };
+
+ // holds a total count of Ajax requests originated from the driver
+ Gitana.requestCount = 0;
+
+ // version of the driver
+ Gitana.VERSION = "1.0.304";
+
+ // allow for optional global assignment
+ // TODO: until we clean up the "window" variable reliance, we have to always set onto window again
+ // TODO: to support loading within NodeJS
+ //if (window && !window.Gitana) {
+ if (window) {
+ window.Gitana = Gitana;
+ }
+
+ // helper function for escaping
+ Gitana.escape = function(text)
+ {
+ if (text) {
+ text = encodeURIComponent(text);
+ }
+
+ return text;
+ };
+
+ /**
+ * Resets the driver (used for test purposes).
+ */
+ Gitana.reset = function()
+ {
+ Gitana.HTTP_TIMEOUT = 120000;
+
+ Gitana.PLATFORM_CACHE("clear");
+ Gitana.deleteCookie("GITANA_TICKET");
+ };
+
+ // insertion point for on-load adjustments (cloudcms-net server)
+ Gitana.__INSERT_MARKER = null;
+
+ // toggles use of GET method when possible (rather than POST)
+ // useful for branch.queryNodes()
+ Gitana.PREFER_GET_OVER_POST = false;
+
+ // whether to set the withCredential flag by default
+ Gitana.XHR_WITH_CREDENTIALS = true;
+
+ // whether to send the special X-CLOUDCMS-ORIGIN header
+ Gitana.HTTP_X_CLOUDCMS_ORIGIN_HEADER = true;
+
+ // method to call when a refresh token fails to acquire the access token
+ Gitana.REFRESH_TOKEN_FAILURE_FN = function(http) {
+ http.clearStorage();
+ Gitana.deleteCookie("GITANA_TICKET");
+ };
+
+ // a way to specify HTTP parameters to attach to every request
+ Gitana.HTTP_PARAMS = {};
+
+ // a way to specify HTTP headers to attach to every request
+ Gitana.HTTP_HEADERS = {};
+
+ // a way to configure the XHR headers ahead of send
+ Gitana.configureRequestHeaders = function(method, url, headers, options)
+ {
+ // no implementation
+ };
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // support for CSRF / XSRF
+ //
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+
+ // the CSRF token can be explicitly stored here if you want to forgo cookies as a storage mechanism
+ Gitana.CSRF_TOKEN = null;
+
+ // these cookies can be consulted by the driver to acquire the csrf token
+ // override this with different cookie names if your framework requires it
+ Gitana.CSRF_COOKIE_NAMES = ["CSRF-TOKEN", "XSRF-TOKEN"];
+
+ // the csrf token is sent over the wire using XHR and this header name
+ Gitana.CSRF_HEADER_NAME = "X-CSRF-TOKEN";
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // default locale - set to undefined to allow the browser to specify, null to override browser with empty
+ //
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+
+ Gitana.DEFAULT_LOCALE = undefined;
+
+ Gitana.defaultErrorHandler = function(err)
+ {
+ if (console && console.warn)
+ {
+ console.warn(err);
+ }
+ };
+
+ // the driver auto-upgrades connections to "api.cloudcms.com" to "api1.cloudcms.com" which is a permanent domain
+ // for our cloudfront-hosted API.
+ // over time, the "api.cloudcms.com" domain will transition to cloudfront as well (by the end of 2017 at the latest)
+ Gitana.AUTO_UPGRADE_TO_CLOUDFRONT = true;
+
+ // allow for custom headers to be sent with OAuth2 token request
+ Gitana.OAUTH2_TOKEN_REQUEST_HEADERS = {};
+
+})(window);
+(function(global) {
+ Gitana.Error = function () {};
+ Gitana.Error.prototype = new Error();
+ Gitana.Error.prototype.constructor = Gitana.Error;
+}(this));(function(window)
+{
+ Gitana.WorkQueue = function(maxSize) {
+
+ if (!maxSize) {
+ maxSize = 3;
+ }
+
+ let blockExecution = false;
+
+ const pendingWorkFns = [];
+ let activeCount = 0;
+
+ const processWork = function () {
+
+ // if another "thread" is running the processor, don't bother
+ if (blockExecution)
+ {
+ return;
+ }
+
+ blockExecution = true;
+
+ // add as many pending work items as we can, loop until full or no more pending
+ let process = true;
+ do
+ {
+ // if nothing to work on, bail
+ if (pendingWorkFns.length === 0)
+ {
+ process = false;
+ }
+
+ // if we're full, bail
+ if (activeCount >= maxSize)
+ {
+ process = false;
+ }
+
+ if (process)
+ {
+ // increment active count
+ activeCount++;
+
+ //console.log("Active work items: " + activeCount);
+
+ // define execution function and splice/bind to 0th element from pending list
+ const executionFn = function(workFn) {
+ return function() {
+ workFn(function () {
+
+ // decrement active count
+ activeCount--;
+
+ //console.log("Active work items: " + activeCount);
+
+ // process more work on timeout
+ window.setTimeout(processWork);
+ });
+
+ };
+ }(pendingWorkFns.splice(0, 1)[0]);
+
+ // execute on timeout
+ window.setTimeout(executionFn);
+ }
+
+ } while (process);
+
+ blockExecution = false;
+ };
+
+ return function(workFn) {
+ pendingWorkFns.push(workFn);
+
+ // execute on timeout
+ window.setTimeout(processWork);
+ };
+
+ };
+
+})(window);
+(function(global)
+{
+ // the default timeout for xhr connections
+ // this is set long at 2 minutes
+ Gitana.HTTP_TIMEOUT = 120000;
+ Gitana.HTTP_TIMEOUT_FN = function(xhr, method, url)
+ {
+ // nothing to do;
+ };
+
+ // set a high limit on concurrent HTTP requests
+ Gitana.HTTP_WORK_QUEUE_SIZE = 100000;
+
+ Gitana.HTTP_XHR_FACTORY = function() {
+ return null;
+ };
+
+ Gitana.Http = Base.extend(
+ /** @lends Gitana.Http.prototype */
+ {
+ /**
+ * @constructs
+ *
+ * @class Gitana.Http
+ */
+ constructor: function()
+ {
+ // the http work queue
+ const enqueue = new Gitana.WorkQueue(Gitana.HTTP_WORK_QUEUE_SIZE);
+
+ ///////////////////////////////////////////////////////////////////////////////////////
+ //
+ // PRIVILEDGED METHODS
+ //
+
+ this.invoke = function(options)
+ {
+ const self = this;
+
+ // add work to be done to the queue
+ enqueue(function(options) {
+ return function(workDoneFn) {
+
+ const method = options.method || 'GET';
+ const url = options.url;
+ const data = options.data;
+ const headers = options.headers || {};
+ let success = options.success || function () {};
+ let failure = options.failure || function () {};
+
+ // wrap a bit further to support release of the http work queue
+ const _success = success;
+ success = function() {
+ workDoneFn();
+ const args = Array.prototype.slice.call(arguments);
+ _success.apply(self, args);
+ };
+ const _failure = failure;
+ failure = function() {
+ workDoneFn();
+ const args = Array.prototype.slice.call(arguments);
+ _failure.apply(self, args);
+ };
+
+ // make sure that all responses come back as JSON if they can (instead of XML)
+ headers["Accept"] = "application/json";
+
+ // ensure that CSRF token is applied (if available)
+ // the csrf token
+ let csrfToken = Gitana.CSRF_TOKEN;
+ if (!csrfToken)
+ {
+ // if we were not explicitly provided the token, look it up from a cookie
+ // NOTE: this only works in the browser
+ for (let t = 0; t < Gitana.CSRF_COOKIE_NAMES.length; t++)
+ {
+ const cookieName = Gitana.CSRF_COOKIE_NAMES[t];
+
+ const cookieValue = Gitana.readCookie(cookieName);
+ if (cookieValue)
+ {
+ csrfToken = cookieValue;
+ break;
+ }
+ }
+ }
+ if (csrfToken)
+ {
+ headers[Gitana.CSRF_HEADER_NAME] = csrfToken;
+ }
+
+ // XHR_CACHE_FN
+ if (typeof(Gitana.XHR_CACHE_FN) !== "undefined" && Gitana.XHR_CACHE_FN !== null)
+ {
+ const responseObject = Gitana.XHR_CACHE_FN({
+ method: method,
+ url: url,
+ headers: headers
+ });
+
+ if (responseObject)
+ {
+ return success(responseObject);
+ }
+ }
+
+ // plug in a special Cloud CMS header to identify the CORS domain of the browser
+ if (Gitana.HTTP_X_CLOUDCMS_ORIGIN_HEADER)
+ {
+ if (typeof(window) !== "undefined")
+ {
+ if (window.location && window.location.origin)
+ {
+ headers["X-CLOUDCMS-ORIGIN"] = window.location.origin;
+ }
+ }
+ }
+
+ const xhr = Gitana.Http.Request();
+
+ if (Gitana.XHR_WITH_CREDENTIALS)
+ {
+ xhr.withCredentials = true;
+ }
+
+ // timeout handler
+ const httpTimeoutFn = function () {
+ xhr.abort();
+
+ if (Gitana.HTTP_TIMEOUT_FN)
+ {
+ Gitana.HTTP_TIMEOUT_FN(xhr, method, url);
+ }
+
+ //console.log("HTTP Request timed out");
+
+ const responseObject = {
+ "timeout": true,
+ "text": "Http Request timed out",
+ "info": {
+ "method": method,
+ "url": url
+ }
+ };
+
+ // everything what is 400 and above is a failure code
+ failure(responseObject, xhr);
+
+ return false;
+ };
+ let httpTimeoutHolder = null;
+ if (Gitana.HTTP_TIMEOUT > 0)
+ {
+ httpTimeoutHolder = setTimeout(httpTimeoutFn, Gitana.HTTP_TIMEOUT);
+ }
+
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4)
+ {
+ let regex = /^(.*?):\s*(.*?)\r?$/mg, requestHeaders = headers, responseHeaders = {}, responseHeadersString = '', match;
+
+ if (!!xhr.getAllResponseHeaders)
+ {
+ responseHeadersString = xhr.getAllResponseHeaders();
+ while ((match = regex.exec(responseHeadersString)))
+ {
+ responseHeaders[match[1]] = match[2];
+ }
+ }
+ else if (!!xhr.getResponseHeaders)
+ {
+ responseHeadersString = xhr.getResponseHeaders();
+ for (let i = 0, len = responseHeadersString.length; i < len; ++i)
+ {
+ responseHeaders[responseHeadersString[i][0]] = responseHeadersString[i][1];
+ }
+ }
+
+ let includeXML = false;
+ if ('Content-Type' in responseHeaders)
+ {
+ if (responseHeaders['Content-Type'] === 'text/xml')
+ {
+ includeXML = true;
+ }
+ }
+
+ const responseObject = {
+ text: xhr.responseText,
+ xml: (includeXML ? xhr.responseXML : ''),
+ requestHeaders: requestHeaders,
+ responseHeaders: responseHeaders
+ };
+
+ // handle the response
+ if (xhr.status === 0)
+ {
+ // not handled
+ }
+ if ((xhr.status >= 200 && xhr.status <= 226) || xhr.status === 304)
+ {
+ if (httpTimeoutHolder)
+ {
+ clearTimeout(httpTimeoutHolder);
+ }
+
+ // XHR_CACHE_FN
+ if (typeof(Gitana.XHR_CACHE_FN) !== "undefined" && Gitana.XHR_CACHE_FN !== null)
+ {
+ Gitana.XHR_CACHE_FN({
+ method: method,
+ url: url,
+ headers: headers
+ }, responseObject);
+ }
+
+ // ok
+ success(responseObject, xhr);
+ }
+ else if (xhr.status >= 400)
+ {
+ if (httpTimeoutHolder)
+ {
+ clearTimeout(httpTimeoutHolder);
+ }
+
+ // everything what is 400 and above is a failure code
+ failure(responseObject, xhr);
+ }
+ else if (xhr.status >= 300 && xhr.status <= 303)
+ {
+ if (httpTimeoutHolder)
+ {
+ clearTimeout(httpTimeoutHolder);
+ }
+
+ // some kind of redirect, probably to a login server
+ // indicates missing access token?
+ failure(responseObject, xhr);
+ }
+ }
+ };
+
+ if (Gitana.configureRequestHeaders)
+ {
+ Gitana.configureRequestHeaders(method, url, headers, options);
+ }
+
+ xhr.open(method, url, true);
+ /*
+ xhr.timeout = Gitana.HTTP_TIMEOUT;
+ xhr.ontimeout = function () {
+ failure({
+ "timeout": true
+ }, xhr);
+ };
+ */
+
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ for (const header in headers)
+ {
+ xhr.setRequestHeader(header, headers[header]);
+ }
+
+ try
+ {
+ xhr.send(data);
+ }
+ catch (e)
+ {
+ console.log(e);
+ }
+ };
+ }(options));
+ };
+ },
+
+ /**
+ * Performs an HTTP call.
+ *
+ * @param options
+ */
+ request: function(options)
+ {
+ return this.invoke(options);
+ }
+ });
+
+ Gitana.Http.toQueryString = function(params)
+ {
+ let queryString = "";
+
+ if (params)
+ {
+ for (const k in params)
+ {
+ if (queryString.length > 0)
+ {
+ queryString += "&";
+ }
+
+ let val = null;
+ if (params[k])
+ {
+ val = params[k];
+
+ // url encode
+ val = Gitana.Http.URLEncode(val);
+ }
+
+ if (val)
+ {
+ queryString += k + "=" + val;
+ }
+ }
+ }
+
+ return queryString;
+ };
+
+ Gitana.Http.Request = function()
+ {
+ let XHR = null;
+
+ // allow for custom XHR factory
+ if (Gitana.HTTP_XHR_FACTORY) {
+ XHR = Gitana.HTTP_XHR_FACTORY();
+ }
+
+ if (!XHR)
+ {
+ // legacy support for titanium
+ if (typeof global.Titanium !== 'undefined' && typeof global.Titanium.Network.createHTTPClient !== 'undefined')
+ {
+ XHR = global.Titanium.Network.createHTTPClient();
+ }
+ else
+ {
+ // W3C (browser)
+ XHR = new global.XMLHttpRequest();
+ }
+ }
+
+ return XHR;
+ };
+
+ const Hash = function() {};
+ Hash.prototype =
+ {
+ join: function(string)
+ {
+ string = string || '';
+ return this.values().join(string);
+ },
+
+ keys: function()
+ {
+ let i, arr = [], self = this;
+ for (i in self) {
+ if (self.hasOwnProperty(i)) {
+ arr.push(i);
+ }
+ }
+
+ return arr;
+ },
+
+ values: function()
+ {
+ let i, arr = [], self = this;
+ for (i in self) {
+ if (self.hasOwnProperty(i)) {
+ arr.push(self[i]);
+ }
+ }
+
+ return arr;
+ },
+ shift: function(){throw 'not implemented';},
+ unshift: function(){throw 'not implemented';},
+ push: function(){throw 'not implemented';},
+ pop: function(){throw 'not implemented';},
+ sort: function(){throw 'not implemented';},
+
+ ksort: function(func){
+ let self = this, keys = self.keys(), i, value, key;
+
+ if (func === undefined) {
+ keys.sort();
+ } else {
+ keys.sort(func);
+ }
+
+ for (i = 0; i < keys.length; i++) {
+ key = keys[i];
+ value = self[key];
+ delete self[key];
+ self[key] = value;
+ }
+
+ return self;
+ },
+ toObject: function () {
+ let obj = {}, i, self = this;
+ for (i in self) {
+ if (self.hasOwnProperty(i)) {
+ obj[i] = self[i];
+ }
+ }
+
+ return obj;
+ }
+ };
+
+ const Collection = function(obj)
+ {
+ let args = arguments, args_callee = args.callee,
+ i, collection = this;
+
+ if (!(this instanceof args_callee)) {
+ return new args_callee(obj);
+ }
+
+ for(i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ collection[i] = obj[i];
+ }
+ }
+
+ return collection;
+ };
+ Collection.prototype = new Hash();
+
+ Gitana.Http.URI = function(url)
+ {
+ let args = arguments, args_callee = args.callee,
+ parsed_uri, scheme, host, port, path, query, anchor,
+ parser = /^([^:\/?#]+?:\/\/)*([^\/:?#]*)?(:[^\/?#]*)*([^?#]*)(\?[^#]*)?(#(.*))*/,
+ uri = this;
+
+ if (!(this instanceof args_callee))
+ {
+ return new args_callee(url);
+ }
+
+ uri.scheme = '';
+ uri.host = '';
+ uri.port = '';
+ uri.path = '';
+ uri.query = new Gitana.Http.QueryString();
+ uri.anchor = '';
+
+ if (url !== null)
+ {
+ parsed_uri = url.match(parser);
+
+ scheme = parsed_uri[1];
+ host = parsed_uri[2];
+ port = parsed_uri[3];
+ path = parsed_uri[4];
+ query = parsed_uri[5];
+ anchor = parsed_uri[6];
+
+ scheme = (scheme !== undefined) ? scheme.replace('://', '').toLowerCase() : 'http';
+ port = (port ? port.replace(':', '') : (scheme === 'https' ? '443' : '80'));
+ // correct the scheme based on port number
+ scheme = (scheme === 'http' && port === '443' ? 'https' : scheme);
+ query = query ? query.replace('?', '') : '';
+ anchor = anchor ? anchor.replace('#', '') : '';
+
+
+ // Fix the host name to include port if non-standard ports were given
+ if ((scheme === 'https' && port !== '443') || (scheme === 'http' && port !== '80')) {
+ host = host + ':' + port;
+ }
+
+ uri.scheme = scheme;
+ uri.host = host;
+ uri.port = port;
+ uri.path = path || '/';
+ uri.query.setQueryParams(query);
+ uri.anchor = anchor || '';
+ }
+ };
+
+ Gitana.Http.URI.prototype = {
+ scheme: '',
+ host: '',
+ port: '',
+ path: '',
+ query: '',
+ anchor: '',
+ toString: function () {
+ const self = this, query = self.query + '';
+ return self.scheme + '://' + self.host + self.path + (query !== '' ? '?' + query : '') + (self.anchor !== '' ? '#' + self.anchor : '');
+ }
+ };
+
+ Gitana.Http.QueryString = function(obj)
+ {
+ let args = arguments, args_callee = args.callee,
+ i, querystring = this;
+
+ if (!(this instanceof args_callee)) {
+ return new args_callee(obj);
+ }
+
+ if (obj !== undefined) {
+ for (i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ querystring[i] = obj[i];
+ }
+ }
+ }
+
+ return querystring;
+ };
+
+ // QueryString is a type of collection So inherit
+ Gitana.Http.QueryString.prototype = new Collection();
+
+ Gitana.Http.QueryString.prototype.toString = function ()
+ {
+ let i, self = this, q_arr = [], ret = '',
+ val = '', encode = Gitana.Http.URLEncode;
+ self.ksort(); // lexicographical byte value ordering of the keys
+
+ for (i in self) {
+ if (self.hasOwnProperty(i)) {
+ if (i !== undefined && self[i] !== undefined) {
+ val = encode(i) + '=' + encode(self[i]);
+ q_arr.push(val);
+ }
+ }
+ }
+
+ if (q_arr.length > 0) {
+ ret = q_arr.join('&');
+ }
+
+ return ret;
+ };
+
+ Gitana.Http.QueryString.prototype.setQueryParams = function (query)
+ {
+ let args = arguments, args_length = args.length, i, query_array,
+ query_array_length, querystring = this, key_value;
+
+ if (args_length === 1) {
+ if (typeof query === 'object') {
+ // iterate
+ for (i in query) {
+ if (query.hasOwnProperty(i)) {
+ querystring[i] = query[i];
+ }
+ }
+ } else if (typeof query === 'string') {
+ // split string on '&'
+ query_array = query.split('&');
+ // iterate over each of the array items
+ for (i = 0, query_array_length = query_array.length; i < query_array_length; i++) {
+ // split on '=' to get key, value
+ key_value = query_array[i].split('=');
+ querystring[key_value[0]] = key_value[1];
+ }
+ }
+ } else {
+ for (i = 0; i < arg_length; i += 2) {
+ // treat each arg as key, then value
+ querystring[args[i]] = args[i+1];
+ }
+ }
+ };
+
+ Gitana.Http.URLEncode = function(string)
+ {
+ function hex(code) {
+ let hex = code.toString(16).toUpperCase();
+ if (hex.length < 2) {
+ hex = 0 + hex;
+ }
+ return '%' + hex;
+ }
+
+ if (!string) {
+ return '';
+ }
+
+ string = string + '';
+ let reserved_chars = /[ \r\n!*"'();:@&=+$,\/?%#\[\]<>{}|`^\\\u0080-\uffff]/,
+ str_len = string.length, i, string_arr = string.split(''), c;
+
+ for (i = 0; i < str_len; i++)
+ {
+ c = string_arr[i].match(reserved_chars);
+ if (c)
+ {
+ c = c[0].charCodeAt(0);
+
+ if (c < 128) {
+ string_arr[i] = hex(c);
+ } else if (c < 2048) {
+ string_arr[i] = hex(192+(c>>6)) + hex(128+(c&63));
+ } else if (c < 65536) {
+ string_arr[i] = hex(224+(c>>12)) + hex(128+((c>>6)&63)) + hex(128+(c&63));
+ } else if (c < 2097152) {
+ string_arr[i] = hex(240+(c>>18)) + hex(128+((c>>12)&63)) + hex(128+((c>>6)&63)) + hex(128+(c&63));
+ }
+ }
+ }
+
+ return string_arr.join('');
+ };
+
+ Gitana.Http.URLDecode = function (string)
+ {
+ if (!string)
+ {
+ return '';
+ }
+
+ return string.replace(/%[a-fA-F0-9]{2}/ig, function (match) {
+ return String.fromCharCode(parseInt(match.replace('%', ''), 16));
+ });
+ };
+
+}(this));
+(function()
+{
+ Gitana.OAuth2Http = Gitana.Http.extend(
+ /** @lends Gitana.OAuth2Http.prototype */
+ {
+ /**
+ * @constructs
+ *
+ * @class Gitana.OAuth2Http
+ */
+ constructor: function(options, storage)
+ {
+ const self = this;
+
+ // storage for OAuth credentials
+ // this can either be a string ("local", "session", "memory") or a storage instance or empty
+ // if empty, memory-based storage is assumed
+ if (storage === null || typeof(storage) === "string")
+ {
+ storage = new Gitana.OAuth2Http.Storage(storage);
+ }
+
+ // cookie mode
+ this.cookieMode = null;
+
+ // ticket mode
+ this.ticketMode = null;
+
+ // preset the error state
+ this.error = null;
+ this.errorDescription = null;
+ this.errorUri = null;
+
+ // gitana urls
+ let tokenURL = "/oauth/token";
+ if (options.tokenURL)
+ {
+ tokenURL = options.tokenURL;
+ }
+
+ // base URL?
+ let baseURL = null;
+ if (options.baseURL)
+ {
+ baseURL = options.baseURL;
+ }
+
+ // client
+ const clientKey = options.clientKey;
+ const clientSecret = options.clientSecret;
+
+ // authorization flow
+ // if none specified, assume AUTHORIZATION CODE
+ this.authorizationFlow = options.authorizationFlow || Gitana.OAuth2Http.AUTHORIZATION_CODE;
+
+ // optional
+ if (options.requestedScope)
+ {
+ this.requestedScope = options.requestedScope;
+ }
+
+ if (this.authorizationFlow === Gitana.OAuth2Http.AUTHORIZATION_CODE)
+ {
+ this.code = options.code;
+ this.redirectUri = options.redirectUri;
+ }
+
+ if (this.authorizationFlow === Gitana.OAuth2Http.PASSWORD)
+ {
+ this.username = options.username;
+
+ if (options.password)
+ {
+ this.password = options.password;
+ }
+ else
+ {
+ this.password = "";
+ }
+ }
+
+ if (this.authorizationFlow === Gitana.OAuth2Http.COOKIE)
+ {
+ this.cookieMode = true;
+ }
+
+ if (this.authorizationFlow === Gitana.OAuth2Http.TICKET)
+ {
+ this.ticketMode = options.ticket;
+ }
+
+ this.ticketMaxAge = options.ticketMaxAge;
+
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ACCESSORS
+ //
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Clears persisted storage of auth data
+ */
+ this.clearStorage = function()
+ {
+ storage.clear();
+ };
+
+ /**
+ * Gets or saves the access token
+ *
+ * @param value {String} optional value
+ */
+ this.accessToken = function(value)
+ {
+ return storage.poke("accessToken", value);
+ };
+
+ /**
+ * Gets or saves the refresh token
+ *
+ * @param value [String] optional value
+ */
+ this.refreshToken = function(value)
+ {
+ return storage.poke("refreshToken", value);
+ };
+
+ /**
+ * Gets or saves the granted scope
+ *
+ * @param value [String] optional value
+ */
+ this.grantedScope = function(value)
+ {
+ return storage.poke("grantedScope", value);
+ };
+
+ /**
+ * Gets or saves the expires in value
+ *
+ * @param value [String] optional value
+ */
+ this.expiresIn = function(value)
+ {
+ return storage.poke("expiresIn", value);
+ };
+
+ /**
+ * Gets or saves the grant time
+ *
+ * @param value [String] optional value
+ */
+ this.grantTime = function(value)
+ {
+ return storage.poke("grantTime", value);
+ };
+
+ this.getClientAuthorizationHeader = function() {
+
+ let basicString = clientKey + ":";
+ if (clientSecret)
+ {
+ basicString += clientSecret;
+ }
+ return "Basic " + Gitana.btoa(basicString);
+ };
+
+ this.getBearerAuthorizationHeader = function()
+ {
+ return "Bearer " + self.accessToken();
+ };
+
+ this.getPrefixedTokenURL = function()
+ {
+ return this.getPrefixedURL(tokenURL);
+ };
+
+ this.getPrefixedURL = function(url)
+ {
+ let rebasedURL = url;
+ if (baseURL && Gitana.startsWith(url, "/"))
+ {
+ rebasedURL = baseURL + url;
+ }
+
+ return rebasedURL;
+ };
+
+
+ // if they initiatialized with an access token, clear and write into persisted state
+ // unless they're continuing an existing token
+ if (this.authorizationFlow === Gitana.OAuth2Http.TOKEN)
+ {
+ const existingAccessToken = this.accessToken();
+ if (existingAccessToken !== options.accessToken)
+ {
+ storage.clear();
+ }
+
+ this.accessToken(options.accessToken);
+ }
+
+ this.base();
+ },
+
+ /**
+ * Performs an HTTP call using OAuth2.
+ *
+ * @param options
+ */
+ request: function(options)
+ {
+ const self = this;
+
+ /**
+ * Call over to Gitana and acquires an access token using flow authorization.
+ *
+ * @param success
+ * @param failure
+ */
+ const doGetAccessToken = function(success, failure)
+ {
+ const onSuccess = function(response, xhr)
+ {
+ const object = JSON.parse(response.text);
+ if (object["error"])
+ {
+ self.error = object["error"];
+ self.errorDescription = object["error_description"];
+ self.errorUri = object["error_uri"];
+
+ return failure(response, xhr);
+ }
+
+ const _accessToken = object["access_token"];
+ const _refreshToken = object["refresh_token"];
+ const _expiresIn = object["expires_in"];
+ const _grantedScope = object["scope"];
+ const _grantTime = new Date().getTime();
+
+ // store into persistent storage
+ self.clearStorage();
+ self.accessToken(_accessToken);
+ self.refreshToken(_refreshToken);
+ self.expiresIn(_expiresIn);
+ self.grantedScope(_grantedScope);
+ self.grantTime(_grantTime);
+
+ // console.log("doGetAccessToken -> " + JSON.stringify(object));
+
+ success();
+ };
+
+ const onFailure = function(http, xhr) {
+ failure(http, xhr);
+ };
+
+ const o = {
+ success: onSuccess,
+ failure: onFailure,
+ headers: {},
+ url: self.getPrefixedTokenURL(),
+ method: Gitana.OAuth2Http.TOKEN_METHOD
+ };
+
+ if (Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
+ {
+ for (var k in Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
+ {
+ o.headers[k] = Gitana.OAUTH2_TOKEN_REQUEST_HEADERS[k];
+ }
+ }
+
+ o.headers["Authorization"] = self.getClientAuthorizationHeader();
+
+ // query string
+ const qs = {};
+
+ // ticket max age
+ if (self.ticketMaxAge)
+ {
+ qs["ticketMaxAge"] = self.ticketMaxAge;
+ }
+
+ // if we're POSTing, do so as application/x-www-form-urlencoded to make secure over the wire
+ if ("post" === Gitana.OAuth2Http.TOKEN_METHOD.toLowerCase())
+ {
+ o.headers["Content-Type"] = "application/x-www-form-urlencoded";
+
+ // url encoded payload
+ const urlEncodedTokens = {};
+ urlEncodedTokens["grant_type"] = self.authorizationFlow;
+ if (self.requestedScope) {
+ urlEncodedTokens["scope"] = self.requestedScope;
+ }
+ if (self.authorizationFlow === Gitana.OAuth2Http.AUTHORIZATION_CODE)
+ {
+ urlEncodedTokens["code"] = self.code;
+ if (self.redirectUri) {
+ urlEncodedTokens["redirect_uri"] = self.redirectUri;
+ }
+ }
+ else if (self.authorizationFlow === Gitana.OAuth2Http.PASSWORD)
+ {
+ urlEncodedTokens["username"] = self.username;
+ urlEncodedTokens["password"] = self.password;
+ }
+ o.data = "" + Gitana.Http.toQueryString(urlEncodedTokens);
+ }
+ else
+ {
+ qs["grant_type"] = self.authorizationFlow;
+ if (self.requestedScope) {
+ qs["scope"] = self.requestedScope;
+ }
+ if (self.authorizationFlow === Gitana.OAuth2Http.AUTHORIZATION_CODE)
+ {
+ qs["code"] = self.code;
+ if (self.redirectUri) {
+ qs["redirect_uri"] = self.redirectUri;
+ }
+ }
+ else if (self.authorizationFlow === Gitana.OAuth2Http.PASSWORD)
+ {
+ qs["username"] = self.username;
+ qs["password"] = self.password;
+ }
+ }
+
+ // append into query string
+ const queryString = Gitana.Http.toQueryString(qs);
+ if (queryString)
+ {
+ if (o.url.indexOf("?") > -1)
+ {
+ o.url = o.url + "&" + queryString;
+ }
+ else
+ {
+ o.url = o.url + "?" + queryString;
+ }
+ }
+
+ self.invoke(o);
+ };
+
+ if (typeof(Gitana.REFRESH_TOKEN_LOCKS) === "undefined")
+ {
+ Gitana.REFRESH_TOKEN_LOCKS = {};
+ }
+ if (typeof(Gitana.REFRESH_TOKEN_LOCK_REATTEMPT_MS) === "undefined")
+ {
+ Gitana.REFRESH_TOKEN_LOCK_REATTEMPT_MS = 75;
+ }
+
+ const waitForPendingRefresh = function(key, oldAccessToken)
+ {
+ setTimeout(function() {
+
+ // if another "thread" is still refreshing, keep on waiting
+ if (Gitana.REFRESH_TOKEN_LOCKS[key]) {
+ return waitForPendingRefresh();
+ }
+
+ // if we get this far, we take advantage of the new access key
+ // first check to make sure that it is a different access key
+ const newAccessToken = self.accessToken();
+
+ // we try the call again under the assumption that the access token is valid
+ // if the access token is different, we allow for another attempted refresh
+ // otherwise we do not to avoid spinning around forever
+ const autoAttemptRefresh = (newAccessToken === oldAccessToken);
+
+ // fire the call
+ doCall(autoAttemptRefresh);
+
+ }, Gitana.REFRESH_TOKEN_LOCK_REATTEMPT_MS);
+ };
+
+ /**
+ * Calls over to Gitana and acquires an access token using an existing refresh token.
+ *
+ * We use a refresh token lock here (scoped to the module) so that only one event loop per refresh token
+ * is allowed to perform the refresh at a time. This is to avoid making excessive network calls and also
+ * helps to avoid race/bounce conditions when multiple refresh tokens come back, spinning things out of
+ * control. Eventually it settles down but better to avoid altogether.
+ *
+ * @param success
+ * @param failure
+ */
+ const doRefreshAccessToken = function(success, failure) {
+
+ const key = self.refreshToken();
+ const oldAccessToken = self.accessToken();
+
+ // if another "thread" is refreshing for this refresh key, then we wait until it finishes
+ // when it finishes, we either use the acquired access token or make another attempt
+ if (Gitana.REFRESH_TOKEN_LOCKS[key]) {
+ return waitForPendingRefresh(key, oldAccessToken);
+ }
+
+ // claim that we are the "thread" doing the refresh
+ Gitana.REFRESH_TOKEN_LOCKS[key] = true;
+
+ // make the http call for the refresh
+ _doRefreshAccessToken(function(response) {
+
+ // all done, delete the lock
+ delete Gitana.REFRESH_TOKEN_LOCKS[key];
+
+ // callback
+ success(response);
+
+ }, function(http, xhr) {
+
+ // didn't work, release the lock
+ delete Gitana.REFRESH_TOKEN_LOCKS[key];
+
+ // callback
+ failure(http, xhr);
+ });
+ };
+
+ const _doRefreshAccessToken = function(success, failure) {
+
+ const onSuccess = function(response)
+ {
+ const object = JSON.parse(response.text);
+ if (response["error"])
+ {
+ self.error = object["error"];
+ self.errorDescription = object["error_description"];
+ self.errorUri = object["error_uri"];
+ }
+ else
+ {
+ const _accessToken = object["access_token"];
+ const _refreshToken = object["refresh_token"];
+ const _expiresIn = object["expires_in"];
+ //self.grantedScope = object["scope"]; // this doesn't come back on refresh, assumed the same
+ const _grantTime = new Date().getTime();
+ const _grantedScope = self.grantedScope();
+
+ // store into persistent storage
+ self.clearStorage();
+ self.accessToken(_accessToken);
+ self.refreshToken(_refreshToken);
+ self.expiresIn(_expiresIn);
+ self.grantedScope(_grantedScope);
+ self.grantTime(_grantTime);
+ }
+
+ success(response);
+ };
+
+ const onFailure = function(http, xhr) {
+
+ Gitana.REFRESH_TOKEN_FAILURE_FN(self, http, xhr);
+
+ failure(http, xhr);
+ };
+
+ const o = {
+ success: onSuccess,
+ failure: onFailure,
+ headers: {},
+ url: self.getPrefixedTokenURL(),
+ method: Gitana.OAuth2Http.TOKEN_METHOD
+ };
+
+ if (Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
+ {
+ for (var k in Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
+ {
+ o.headers[k] = Gitana.OAUTH2_TOKEN_REQUEST_HEADERS[k];
+ }
+ }
+
+ o.headers["Authorization"] = self.getClientAuthorizationHeader();
+
+ // query string
+ const qs = {};
+
+ // ticket max age
+ if (self.ticketMaxAge)
+ {
+ qs["ticketMaxAge"] = self.ticketMaxAge;
+ }
+
+ // if we're POSTing, do so as application/x-www-form-urlencoded to make secure over the wire
+ if ("post" === Gitana.OAuth2Http.TOKEN_METHOD.toLowerCase())
+ {
+ o.headers["Content-Type"] = "application/x-www-form-urlencoded";
+
+ // url encoded payload
+ const urlEncodedTokens = {};
+ urlEncodedTokens["grant_type"] = "refresh_token";
+ urlEncodedTokens["refresh_token"] = self.refreshToken();
+ if (self.requestedScope)
+ {
+ urlEncodedTokens["scope"] = self.requestedScope;
+ }
+ o.data = "" + Gitana.Http.toQueryString(urlEncodedTokens);
+ }
+ else
+ {
+ qs["grant_type"] = "refresh_token";
+ qs["refresh_token"] = self.refreshToken();
+ if (self.requestedScope)
+ {
+ qs["scope"] = self.requestedScope;
+ }
+ }
+
+ // append into query string
+ const queryString = Gitana.Http.toQueryString(qs);
+ if (queryString)
+ {
+ if (o.url.indexOf("?") > -1)
+ {
+ o.url = o.url + "&" + queryString;
+ }
+ else
+ {
+ o.url = o.url + "?" + queryString;
+ }
+ }
+
+ self.invoke(o);
+ };
+
+ const doCall = function(autoAttemptRefresh)
+ {
+ const successHandler = function(response)
+ {
+ options.success(response);
+ };
+
+ const failureHandler = function(http, xhr)
+ {
+ if (autoAttemptRefresh)
+ {
+ // there are a few good reasons why this might naturally fail
+ //
+ // 1. our access token is invalid, has expired or has been forcefully invalidated on the Cloud CMS server
+ // in this case, we get back a 200 and something like http.text =
+ // {"error":"invalid_token","error_description":"Invalid access token: blahblahblah"}
+ //
+ // 2. the access token no longer permits access to the resource
+ // in this case, we get back a 401
+ // it might not make much sense to re-request a new access token, but we do just in case.
+
+ let notJson = false;
+ let isInvalidToken = false;
+ if (http.text)
+ {
+ let responseData = {};
+
+ // catch if http.text is not JSON
+ try
+ {
+ responseData = JSON.parse(http.text);
+ }
+ catch (e)
+ {
+ console.log("Error response is not json");
+ console.log(e);
+ console.log(http.text);
+ notJson = true;
+ }
+
+ if (responseData.error)
+ {
+ if (responseData.error === "invalid_token")
+ {
+ isInvalidToken = true;
+ }
+ }
+ }
+ const is401 = (http.code === 401);
+ const is400 = (http.code === 400);
+ const is403 = (http.code === 403);
+ const isTimeout = http.timeout;
+
+ // handle both cases
+ if (is401 || is400 || is403 || isInvalidToken || (notJson && !isTimeout))
+ {
+ if (self.refreshToken())
+ {
+ // use the refresh token to acquire a new access token
+ doRefreshAccessToken(function() {
+
+ // success, got a new access token
+
+ doCall(false);
+
+ }, function() {
+
+ // failure, nothing else we can do
+ // call into intended failure handler with the original failure http object
+ options.failure(http, xhr);
+ });
+ }
+ else
+ {
+ // fail case - nothing we can do
+ options.failure(http, xhr);
+ }
+ }
+ else
+ {
+ // some other kind of error
+ options.failure(http, xhr);
+ }
+ }
+ else
+ {
+ // we aren't allowed to automatically attempt to get a new token via refresh token
+ options.failure(http, xhr);
+ }
+ };
+
+ // call through to the protected resource (with custom success/failure handling)
+ const o = {};
+ Gitana.copyInto(o, options);
+ o.success = successHandler;
+ o.failure = failureHandler;
+ if (!o.headers)
+ {
+ o.headers = {};
+ }
+ if (Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
+ {
+ for (var k in Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
+ {
+ o.headers[k] = Gitana.OAUTH2_TOKEN_REQUEST_HEADERS[k];
+ }
+ }
+ if (!self.cookieMode && !self.ticketMode)
+ {
+ o.headers["Authorization"] = self.getBearerAuthorizationHeader();
+ }
+ if (self.ticketMode)
+ {
+ o.headers["GITANA_TICKET"] = encodeURIComponent(self.ticketMode);
+ }
+ o.url = self.getPrefixedURL(o.url);
+
+ // make the call
+ self.invoke(o);
+ };
+
+ // if we have an access token and it's about to expire (within 20 seconds of it's expiry),
+ // we force an early refresh of the access token so that concurrent requests don't get access problems
+ // this is important for any browser-originated requests that rely on a persisted cookie (GITANA_TICKET)
+ //
+ // also provide some debugging if needed
+ let forceRefresh = false;
+ if (self.accessToken())
+ {
+ const grantTime = self.grantTime();
+ if (grantTime)
+ {
+ const expiresIn = self.expiresIn();
+ if (expiresIn)
+ {
+ // NOTE: expiresIn is in seconds
+ const expirationTimeMs = self.grantTime() + (self.expiresIn() * 1000);
+ const nowTimeMs = new Date().getTime();
+
+ const timeRemainingMs = expirationTimeMs - nowTimeMs;
+ if (timeRemainingMs <= 0)
+ {
+ // console.log("Access Token is expired, refresh will be attempted!");
+ }
+ else
+ {
+ // console.log("Access Token Time Remaining: " + timeRemainingMs);
+ }
+
+ if (timeRemainingMs <= 20000)
+ {
+ // console.log("Access Token only has 20 seconds left, forcing early refresh");
+ forceRefresh = true;
+ }
+ }
+ }
+ }
+
+ // if no access token, request one
+ if ((!self.accessToken() || forceRefresh) && !this.cookieMode && !this.ticketMode)
+ {
+ if (!self.refreshToken())
+ {
+ // no refresh token, do an authorization call
+ doGetAccessToken(function() {
+
+ // got an access token, so proceed
+ doCall(true);
+
+ }, function(http, xhr) {
+
+ // access denied
+ options.failure(http, xhr);
+
+ });
+ }
+ else
+ {
+ // we have a refresh token, so do a refresh call
+ doRefreshAccessToken(function() {
+
+ // got an access token, so proceed
+ doCall(true);
+
+ }, function(http, xhr) {
+
+ // unable to get an access token
+ options.failure(http, xhr);
+
+ });
+ }
+ }
+ else
+ {
+ // we already have an access token
+ doCall(true);
+ }
+ },
+
+ /**
+ * Refreshes the OAuth2 access token.
+ */
+ refresh: function(callback)
+ {
+ const self = this;
+
+ const currentRefreshToken = self.refreshToken();
+ if (!currentRefreshToken)
+ {
+ return callback({
+ "message": "The driver does not have a refresh token, cannot refresh"
+ });
+ }
+
+ const onSuccess = function(response)
+ {
+ const object = JSON.parse(response.text);
+ if (object["error"])
+ {
+ self.error = object["error"];
+ self.errorDescription = object["error_description"];
+ self.errorUri = object["error_uri"];
+
+ return callback({
+ "error": self.error,
+ "message": self.errorDescription
+ });
+ }
+ else
+ {
+ const _accessToken = object["access_token"];
+ const _refreshToken = object["refresh_token"];
+ const _expiresIn = object["expires_in"];
+ //self.grantedScope = object["scope"]; // this doesn't come back on refresh, assumed the same
+ const _grantTime = new Date().getTime();
+ const _grantedScope = self.grantedScope();
+
+ // store into persistent storage
+ self.clearStorage();
+ self.accessToken(_accessToken);
+ self.refreshToken(_refreshToken);
+ self.expiresIn(_expiresIn);
+ self.grantedScope(_grantedScope);
+ self.grantTime(_grantTime);
+
+ callback();
+ }
+ };
+
+ const onFailure = function(http, xhr)
+ {
+ if (Gitana.REFRESH_TOKEN_FAILURE_FN)
+ {
+ Gitana.REFRESH_TOKEN_FAILURE_FN(self, http, xhr);
+ }
+
+ // clear storage
+ self.clearStorage();
+
+ callback({
+ "message": "Unable to refresh access token"
+ });
+ };
+
+ const o = {
+ success: onSuccess,
+ failure: onFailure,
+ headers: {},
+ url: self.getPrefixedTokenURL(),
+ method: Gitana.OAuth2Http.TOKEN_METHOD
+ };
+
+ if (Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
+ {
+ for (var k in Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
+ {
+ o.headers[k] = Gitana.OAUTH2_TOKEN_REQUEST_HEADERS[k];
+ }
+ }
+
+ o.headers["Authorization"] = self.getClientAuthorizationHeader();
+
+ // query string
+ const qs = {};
+
+ // ticket max age
+ if (self.ticketMaxAge)
+ {
+ qs["ticketMaxAge"] = self.ticketMaxAge;
+ }
+
+ // if we're POSTing, do so as application/x-www-form-urlencoded to make secure over the wire
+ if ("post" === Gitana.OAuth2Http.TOKEN_METHOD.toLowerCase())
+ {
+ o.headers["Content-Type"] = "application/x-www-form-urlencoded";
+
+ // url encoded data
+ const urlEncodedTokens = {};
+ urlEncodedTokens["grant_type"] = "refresh_token";
+ urlEncodedTokens["refresh_token"] = self.refreshToken();
+ if (self.requestedScope)
+ {
+ urlEncodedTokens["scope"] = self.requestedScope;
+ }
+ o.data = "" + Gitana.Http.toQueryString(urlEncodedTokens);
+ }
+ else
+ {
+ qs["grant_type"] = "refresh_token";
+ qs["refresh_token"] = self.refreshToken();
+ if (self.requestedScope)
+ {
+ qs["scope"] = self.requestedScope;
+ }
+ }
+
+ // append into query string
+ const queryString = Gitana.Http.toQueryString(qs);
+ if (queryString)
+ {
+ if (o.url.indexOf("?") > -1)
+ {
+ o.url = o.url + "&" + queryString;
+ }
+ else
+ {
+ o.url = o.url + "?" + queryString;
+ }
+ }
+
+ self.invoke(o);
+ }
+
+ });
+
+ /**
+ * Provides a storage location for OAuth2 credentials
+ *
+ * @param scope
+ *
+ * @return storage instance
+ * @constructor
+ */
+ Gitana.OAuth2Http.Storage = function(scope)
+ {
+ // in-memory implementation of HTML5 storage interface
+ const memoryStorage = function() {
+
+ const memory = {};
+
+ const m = {};
+ m.removeItem = function(key)
+ {
+ delete memory[key];
+ };
+
+ m.getItem = function(key)
+ {
+ return memory[key];
+ };
+
+ m.setItem = function(key, value)
+ {
+ memory[key] = value;
+ };
+
+ return m;
+ }();
+
+ /**
+ * Determines whether the current runtime environment supports HTML5 local storage
+ *
+ * @return {Boolean}
+ */
+ const supportsLocalStorage = function()
+ {
+ try {
+ return 'localStorage' in window && window['localStorage'] !== null;
+ } catch (e) {
+ return false;
+ }
+ };
+
+ /**
+ * Determines whether the current runtime environment supports HTML5 session storage.
+ *
+ * @return {Boolean}
+ */
+ const supportsSessionStorage = function()
+ {
+ try {
+ return 'sessionStorage' in window && window['sessionStorage'] !== null;
+ } catch (e) {
+ return false;
+ }
+ };
+
+ const acquireStorage = function()
+ {
+ let storage = null;
+
+ // store
+ if (scope === "session" && supportsSessionStorage())
+ {
+ storage = sessionStorage;
+ }
+ else if (scope === "local" && supportsLocalStorage())
+ {
+ storage = localStorage;
+ }
+ else
+ {
+ // fall back to memory-based storage
+ storage = memoryStorage;
+ }
+
+ return storage;
+ };
+
+ // result object
+ const r = {};
+
+ /**
+ * Clears state.
+ */
+ r.clear = function()
+ {
+ // we first set to empty to account for a bug in Chrome
+ // this bug is with the removeItem method where it sometimes doesn't work, so force to empty to handle worst case
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=765524
+ acquireStorage().setItem("gitanaAuthState", "");
+
+ // now do the actual remove
+ acquireStorage().removeItem("gitanaAuthState");
+ };
+
+ /**
+ * Pokes and peeks the value of a key in the state.
+ *
+ * @param key
+ * @param value
+ *
+ * @return {*}
+ */
+ r.poke = function(key, value)
+ {
+ let state = {};
+
+ const stateString = acquireStorage().getItem("gitanaAuthState");
+ if (stateString && stateString !== "") {
+ state = JSON.parse(stateString);
+ }
+
+ let touch = false;
+ if (typeof(value) !== "undefined" && value !== null)
+ {
+ state[key] = value;
+ touch = true;
+ }
+ else if (value === null)
+ {
+ delete state[key];
+ touch = true;
+ }
+
+ if (touch) {
+ acquireStorage().setItem("gitanaAuthState", JSON.stringify(state));
+ }
+
+ return state[key];
+ };
+
+ return r;
+ };
+
+}(this));
+
+// statics
+Gitana.OAuth2Http.PASSWORD = "password";
+Gitana.OAuth2Http.AUTHORIZATION_CODE = "authorization_code";
+Gitana.OAuth2Http.TOKEN = "token";
+Gitana.OAuth2Http.COOKIE = "cookie";
+Gitana.OAuth2Http.TICKET = "ticket";
+
+// method to use for retrieving access and refresh tokens
+//Gitana.OAuth2Http.TOKEN_METHOD = "GET";
+Gitana.OAuth2Http.TOKEN_METHOD = "POST";
+
+
+
+(function(window)
+{
+ /**
+ * Creates a chain. If an object is provided, the chain is augmented onto the object.
+ *
+ * @param object
+ * @param skipAutoTrap
+ */
+ Chain = function(object, skipAutoTrap)
+ {
+ if (!object)
+ {
+ object = {};
+ }
+
+ // wraps the object into a proxy
+ const proxiedObject = Chain.proxy(object);
+
+ // the following methods get pushed onto the chained object
+ // methods for managing chain state
+ proxiedObject.__queue = (function() {
+ let queue = [];
+ return function(x) {
+ if (x) { if (x === 'empty') { queue = []; } else { queue.push(x); }}
+ return queue;
+ };
+ })();
+ proxiedObject.__response = (function() {
+ let response = null;
+ return function(x) {
+ if (!Gitana.isUndefined(x)) { if (x) { response = x; } else { response = null; } }
+ return response;
+ };
+ })();
+ proxiedObject.__waiting = (function() {
+ let waiting = false;
+ return function(x) {
+ if (!Gitana.isUndefined(x)) { waiting = x; }
+ return waiting;
+ };
+ })();
+ proxiedObject.__parent = (function() {
+ let parent = null;
+ return function(x) {
+ if (!Gitana.isUndefined(x)) { if (x) { parent = x; } else { parent = null; } }
+ return parent;
+ };
+ })();
+ proxiedObject.__id = (function() {
+ let id = Chain.idCount;
+ Chain.idCount++;
+ return function() {
+ return id;
+ };
+ })();
+ proxiedObject.__helper = (function() {
+ let helper = null;
+ return function(x) {
+ if (x) { helper = x; }
+ return helper;
+ };
+ })();
+ // marks any chain links which are placeholders for functions
+ proxiedObject.__transparent = (function() {
+ let transparent = false; // assume not transparent
+ return function(x) {
+ if (!Gitana.isUndefined(x)) { transparent = x; }
+ return transparent;
+ };
+ })();
+ // provides consume behavior for copy into (from another object into this one)
+ if (!proxiedObject.__copyState) {
+ proxiedObject.__copyState = function(other) {
+ Gitana.copyInto(this, other);
+ };
+ }
+
+
+
+
+ /**
+ * Queues either a callback function, an array of callback functions or a subchain.
+ *
+ * @param element
+ * @param [functionName] function name for debugging
+ */
+ proxiedObject.then = function(element, functionName)
+ {
+ const self = this;
+
+ let autorun = false;
+
+ //
+ // ARRAY
+ //
+ // if we're given an array of functions, we'll automatically build out a function that orchestrates
+ // the concurrent execution of parallel chains.
+ //
+ // the function will be pushed onto the queue
+ //
+ if (Gitana.isArray(element))
+ {
+ const array = element;
+
+ // parallel function invoker
+ const parallelInvoker = function()
+ {
+ // build out parallel functions
+ const fns = [];
+ for (let i = 0; i < array.length; i++)
+ {
+ const fn = function(func)
+ {
+ return function(done)
+ {
+ // each function gets loaded onto its own "parallel" chain
+ // the parallel chain contains a subchain and the onComplete method
+ // the parallel chain is a clone of this chain
+ // the subchain runs the function
+ // these are serial so that the subchain must complete before the onComplete method is called
+ const parallelChain = Chain(); // note: empty chain (parent)
+ parallelChain.__waiting(true); // this prevents auto-run (which would ground out the first subchain call)
+ parallelChain.subchain(self).then(function () { // TODO: should we self.clone() for parallel operations?
+ func.call(this);
+ });
+ parallelChain.then(function () {
+ done();
+ });
+ parallelChain.__waiting(false); // switch back to normal
+ parallelChain.run();
+ };
+ }(array[i]);
+ fns.push(fn);
+ }
+
+ let count = 0;
+ const total = fns.length;
+ const onComplete = function()
+ {
+ count++;
+ if (count === total)
+ {
+ // manually signal that we're done
+ self.next();
+ }
+ };
+
+ // run all of the functions in parallel
+ for (let i = 0; i < fns.length; i++)
+ {
+ window.setTimeout(function(fn) {
+ return function() {
+ fn(function() {
+ onComplete();
+ });
+ };
+ }(fns[i]));
+ }
+
+ // return false so that we wait for manual self.next() signal
+ return false;
+ };
+
+ // build a subchain (transparent)
+ const subchain = this.subchain(null, true); // don't auto add, we'll do it ourselves
+ subchain.__queue(parallelInvoker);
+ if (functionName) { subchain.__helper(functionName); }
+ element = subchain;
+ }
+
+ //
+ // FUNCTION
+ //
+ // if we're given a function, then we're being asked to execute a function serially.
+ // to facilitate this, we'll wrap it in a subchain and push the subchain down into the queue.
+ // the reason is just to make things a little easier and predictive of what the end user might do with
+ // the chain. they probably don't expect it to just exit out if they try to to
+ // this.then(something)
+ // in other words, they should always feel like they have their own chain (which in fact they do)
+ else if (Gitana.isFunction(element))
+ {
+ // create the subchain
+ // this does a re-entrant call that adds it to the queue (as a subchain)
+ const subchain = this.subchain(null, true); // don't auto add, we'll do it ourselves
+ subchain.__queue(element);
+ if (functionName) { subchain.__helper(functionName); }
+ element = subchain;
+
+ // note: because we're given a function, we can tell this chain to try to "autorun"
+ autorun = true;
+ }
+
+
+ // anything that arrives this far is just a subchain
+ this.__queue(element);
+
+
+ // if we're able to autorun (meaning that we were told to then() a function)...
+ // we climb the parents until we find the topmost parent and tell it to run.
+ if (autorun && !this.__waiting())
+ {
+ let runner = this;
+ while (runner.__parent())
+ {
+ runner = runner.__parent();
+ }
+
+ if (!runner.__waiting())
+ {
+ runner.run();
+ }
+ }
+
+ // always hand back reference to ourselves
+ return this;
+ };
+
+ /**
+ * Run the next element in the queue
+ */
+ proxiedObject.run = function()
+ {
+ const self = this;
+
+ // short cut, if nothing in the queue, bail
+ if (this.__queue().length === 0 || this.__waiting())
+ {
+ return this;
+ }
+
+ // mark that we're running something
+ this.__waiting(true);
+
+ // the element to run
+ const element = this.__queue().shift();
+
+ // case: callback function
+ if (Gitana.isFunction(element))
+ {
+ // it's a callback function
+ const callback = element;
+
+ // try to determine response and previous response
+ let response = null;
+ let previousResponse = null;
+ if (this.__parent())
+ {
+ response = this.__parent().__response();
+ if (this.__parent().__parent())
+ {
+ previousResponse = this.__parent().__parent().__response();
+ }
+ }
+
+ // async
+ window.setTimeout(function()
+ {
+ Chain.log(self, (self.__helper() ? self.__helper()+ " " : "") + "> " + element.toString());
+
+ // execute with "this = chain"
+ const returned = callback.call(self, response, previousResponse);
+ if (returned !== false)
+ {
+ self.next(returned);
+ }
+ }, 0);
+ }
+ else
+ {
+ // it's a subchain element (object)
+ // we make sure to copy response forward
+ const subchain = element;
+ subchain.__response(this.__response());
+
+ // pre-emptively copy forward into subchain
+ // only do this if the subchain is transparent
+ if (subchain.__transparent())
+ {
+ //Gitana.copyInto(subchain, this);
+ subchain.__copyState(this);
+ }
+
+ // BEFORE CHAIN RUN CALLBACK
+ // this provides a way for a chained object to adjust its method signatures and state ahead
+ // of actually executing, usually based on some data that was loaded (such as the type of object
+ // like a domain user or group)
+ //
+ if (subchain.beforeChainRun)
+ {
+ subchain.beforeChainRun.call(subchain);
+ }
+
+ subchain.run();
+ }
+
+ return this;
+ };
+
+ /**
+ * Creates a subchain and adds it to the queue.
+ *
+ * If no argument is provided, the generated subchain will be cloned from the current chain element.
+ */
+ proxiedObject.subchain = function(object, noAutoAdd)
+ {
+ let transparent = false;
+ if (!object) {
+ transparent = true;
+ }
+
+ if (!object)
+ {
+ object = this;
+ }
+
+ const subchain = Chain(object, true);
+ subchain.__parent(this);
+
+ // BEFORE CHAIN RUN CALLBACK
+ // this provides a way for a chained object to adjust its method signatures and state ahead
+ // of actually executing, usually based on some data that was loaded (such as the type of object
+ // like a domain user or group)
+ //
+ if (subchain.beforeChainRun)
+ {
+ subchain.beforeChainRun.call(subchain);
+ }
+
+ if (!noAutoAdd)
+ {
+ this.then(subchain);
+ }
+
+ subchain.__transparent(transparent);
+
+ return subchain;
+ };
+
+ /**
+ * Completes the current element in the chain and provides the response that was generated.
+ *
+ * The response is pushed into the chain as the current response and the current response is bumped
+ * back as the previous response.
+ *
+ * If the response is null, nothing will be bumped.
+ *
+ * @param {Object} response
+ */
+ proxiedObject.next = function(response)
+ {
+ // toggle responses
+ if (typeof response !== "undefined")
+ {
+ this.__response(response);
+ }
+
+ // no longer processing callback
+ this.__waiting(false);
+
+ // if there isn't anything left in the queue, then we're done
+ // if we have a parent, we can signal that we've completed
+ if (this.__queue().length === 0)
+ {
+ if (this.__parent())
+ {
+ // copy response up to parent
+ const r = this.__response();
+ this.__parent().__response(r);
+ this.__response(null);
+
+ // if the current node is transparent, then copy back to parent
+ //if (this.__transparent())
+ if (this.__transparent())
+ {
+ Gitana.deleteProperties(this.__parent());
+ //Gitana.copyInto(this.__parent(), this);
+ this.__parent().__copyState(this);
+ }
+
+ // inform parent that we're done
+ this.__parent().next();
+ }
+
+ // clear parent so that this chain can be relinked
+ this.__parent(null);
+ this.__queue('empty');
+ }
+ else
+ {
+ // run the next element in the queue
+ this.run();
+ }
+ };
+
+ /**
+ * Tells the chain to sleep the given number of milliseconds
+ */
+ proxiedObject.wait = function(ms)
+ {
+ return this.then(function() {
+
+ const wake = function(chain)
+ {
+ return function()
+ {
+ chain.next();
+ };
+ }(this);
+
+ window.setTimeout(wake, ms);
+
+ return false;
+ });
+ };
+
+ /**
+ * Registers an error handler;
+ *
+ * @param errorHandler
+ */
+ proxiedObject.trap = function(errorHandler)
+ {
+ this.errorHandler = errorHandler;
+
+ return this;
+ };
+
+ /**
+ * Handles the error.
+ *
+ * @param err
+ */
+ proxiedObject.error = function(err)
+ {
+ // find the first error handler we can walking up the chain
+ let errorHandler = null;
+ let ancestor = this;
+ while (ancestor && !errorHandler)
+ {
+ errorHandler = ancestor.errorHandler;
+ if (!errorHandler)
+ {
+ ancestor = ancestor.__parent();
+ }
+ }
+
+ // clean up the chain so that it can still be used
+ this.__queue('empty');
+ this.__response(null);
+
+ // disconnect and stop the parent from processing
+ if (this.__parent())
+ {
+ this.__parent().__queue('empty');
+ this.__parent().__waiting(false);
+ }
+
+ if (!errorHandler)
+ {
+ errorHandler = Gitana.defaultErrorHandler;
+ }
+
+ // invoke error handler
+ const code = errorHandler.call(this, err);
+
+ // finish out the chain if we didn't get "false"
+ if (code !== false)
+ {
+ this.next();
+ }
+ };
+
+ /**
+ * Completes a chain and hands control back up to the parent.
+ */
+ proxiedObject.done = function()
+ {
+ return this.__parent();
+ };
+
+ /**
+ * Creates a new chain for this object
+ */
+ proxiedObject.chain = function()
+ {
+ return Chain(this, true).then(function() {
+ // empty chain to kick start
+ });
+ };
+
+
+ // each object that gets chained provides a clone() method
+ // if there is already a clone property, don't override it
+ // this allows implementation classes to control how they get cloned
+ if (!proxiedObject.clone)
+ {
+ /**
+ * Clones this chain and resets chain properties.
+ */
+ proxiedObject.clone = function()
+ {
+ return Chain.clone(this);
+ };
+ }
+
+ // apply auto trap?
+ if (!skipAutoTrap && autoTrap())
+ {
+ proxiedObject.trap(autoTrap());
+ }
+
+ return proxiedObject;
+ };
+
+ /**
+ * Wraps the given object into a proxy.
+ *
+ * If the object is an existing proxy, it is unpackaged and re-proxied.
+ * @param o
+ */
+ Chain.proxy = function(o)
+ {
+ if (o.__original && o.__original())
+ {
+ // NOTE: we can't just unproxy since that loses all state of the current object
+
+ // unproxy back to original
+ //o = Chain.unproxy(o);
+
+ // for now, we can do this?
+ delete o.__original;
+ }
+
+ // clone the object using clone() method
+ let proxy = null;
+ if (o.clone) {
+ proxy = o.clone();
+ } else {
+ proxy = Chain.clone(o);
+ }
+ proxy.__original = function() {
+ return o;
+ };
+
+ return proxy;
+ };
+
+ /**
+ * Hands back the original object for a proxy.
+ *
+ * @param proxy
+ */
+ Chain.unproxy = function(proxy)
+ {
+ let o = null;
+
+ if (proxy.__original && proxy.__original())
+ {
+ o = proxy.__original();
+ }
+
+ return o;
+ };
+
+ Chain.debug = false;
+ Chain.log = function(chain, text)
+ {
+ if (Chain.debug && !Gitana.isUndefined(console))
+ {
+ const f = function()
+ {
+ let identifier = this.__id();
+ if (this.__transparent()) {
+ identifier += "[t]";
+ }
+
+ if (!this.__parent())
+ {
+ return identifier;
+ }
+
+ return f.call(this.__parent()) + " > " + identifier;
+ };
+
+ const identifier = f.call(chain);
+
+ console.log("Chain[" + identifier + "] " + text);
+ }
+ };
+ // clone workhorse method
+ Chain.clone = function(object)
+ {
+ // based on Crockford's solution for clone using prototype on function
+ // this copies all properties and methods
+ // includes copies of chain functions
+ function F() {}
+ F.prototype = object;
+ const clone = new F();
+
+ // copy properties
+ Gitana.copyInto(clone, object);
+
+ return clone;
+ };
+
+ let autoTrapValue = null;
+ const autoTrap = Chain.autoTrap = function(_autoTrap)
+ {
+ if (_autoTrap)
+ {
+ autoTrapValue = _autoTrap;
+ }
+
+ return autoTrapValue;
+ };
+
+ Chain.idCount = 0;
+
+})(window);(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.Chainable = Base.extend(
+ /** @lends Gitana.Chainable.prototype */
+ {
+ /**
+ * @constructs
+ *
+ * @param {Gitana.Driver} driver
+ *
+ * @class Provides common chaining functions used by various interface methods
+ */
+ constructor: function(driver)
+ {
+ this.base();
+
+ /**
+ * Override the Chain.__copyState method so that it utilizes a base method that we can override
+ * on a per-class basis.
+ */
+ this.__copyState = function(other) {
+ Gitana.copyInto(this, other);
+ this.chainCopyState(other);
+ };
+
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // privileged methods
+ //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.getDriver = function()
+ {
+ return driver;
+ };
+
+ this.getFactory = function()
+ {
+ return new Gitana.ObjectFactory();
+ };
+
+ this.httpError = function(httpError)
+ {
+ const err = new Gitana.Error();
+ err.name = "Http Error";
+ err.message = httpError.message;
+ err.status = httpError.status;
+ err.statusText = httpError.statusText;
+
+ if (httpError.errorType)
+ {
+ err.errorType = httpError.errorType;
+ }
+
+ // stack trace might be available
+ if (httpError.stacktrace)
+ {
+ err.stacktrace = httpError.stacktrace;
+ }
+
+ // info that might come back
+ if (httpError.info)
+ {
+ err.info = httpError.info;
+ }
+
+ // any text? if so, capture it
+ if (httpError.response && httpError.response.text)
+ {
+ err.responseText = httpError.response.text;
+
+ // and also make an attempt to convert to JSON data
+ try {
+ err.responseData = JSON.parse(err.responseText);
+ } catch (e) { }
+ }
+
+ this.error(err);
+
+ return false;
+ };
+
+ this.missingNodeError = function(id)
+ {
+ const err = new Gitana.Error();
+ err.name = "Missing Node error";
+ err.message = "The node: " + id + " could not be found";
+
+ this.error(err);
+
+ return false;
+ };
+
+
+
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // PRIVILEGED
+ // CHAIN HANDLERS
+ //
+ /////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Performs a GET from the server and populates the chainable.
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ */
+ this.chainGet = function(chainable, uri, params)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ driver.gitanaGet(uri, params, {}, function(response) {
+ chain.handleResponse(response);
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Creates an object on the server (write + read).
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param object
+ * @param uri
+ * @param params
+ */
+ this.chainCreate = function(chainable, object, uri, params)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ // create
+ driver.gitanaPost(uri, params, object, function(status) {
+ driver.gitanaGet(uri + "/" + status.getId(), null, {}, function(response) {
+ chain.handleResponse(response);
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+
+ }, "chainCreate");
+ };
+
+ /**
+ * Creates an object on the server using one URL and then reads it back using another URL.
+ * This exists because the security responses don't include _doc fields like other responses.
+ *
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param object
+ * @param createUri
+ * @param readUri
+ */
+ this.chainCreateEx = function(chainable, object, createUri, readUri)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(createUri)) {
+ createUri = createUri.call(self);
+ }
+
+ // create
+ driver.gitanaPost(createUri, null, object, function(status) {
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(readUri)) {
+ readUri = readUri.call(self, status);
+ }
+
+ driver.gitanaGet(readUri, null, {}, function(response) {
+ chain.handleResponse(response);
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Performs a POST to the server and populates the chainable with results.
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ * @param payload
+ * @param handleFn
+ */
+ this.chainPost = function(chainable, uri, params, payload, handleFn)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ // create
+ driver.gitanaPost(uri, params, payload, function(response) {
+ if (handleFn) {
+ handleFn(chain, response);
+ } else {
+ chain.handleResponse(response);
+ }
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Performs a POST to the server. The response is not handled.
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ * @param payload (optional)
+ * @param contentType (optional) - example "text/plain"
+ */
+ this.chainPostEmpty = function(chainable, uri, params, payload, contentType)
+ {
+ const self = this;
+
+ // if no payload, set to empty
+ if (!payload && contentType)
+ {
+ if (contentType.indexOf("text/") > -1)
+ {
+ payload = "";
+ }
+ else if (contentType === "application/json")
+ {
+ payload = {};
+ }
+ }
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ // create
+ driver.gitanaPost(uri, params, payload, function() {
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Performs a POST to the server. The response is not handled.
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ * @param contentType (optional) - example "text/plain"
+ * @param payload (optional)
+ */
+ this.chainUpload = function(chainable, uri, params, contentType, payload)
+ {
+ const self = this;
+
+ // if no payload, set to empty
+ if (!payload && contentType)
+ {
+ if (contentType.indexOf("text/") > -1)
+ {
+ payload = "";
+ }
+ else if (contentType === "application/json")
+ {
+ payload = {};
+ }
+ }
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ // create
+ driver.gitanaUpload(uri, params, contentType, payload, function() {
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Performs a GET to the server and pushes the response into the chain.
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ */
+ this.chainGetResponse = function(chainable, uri, params)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ driver.gitanaGet(uri, params, {}, function(response) {
+ chain.next(response);
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Performs a GET to the server and pushes the text response to the callback.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ */
+ this.chainGetResponseText = function(chainable, uri, params)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ driver.gitanaRequest("GET", uri, params, "text/plain", null, {}, function(response) {
+ chain.next(response);
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Performs a GET to the server and pushes the "rows" response attribute into the chain.
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ */
+ this.chainGetResponseRows = function(chainable, uri, params)
+ {
+ return this.chainGetResponse(chainable, uri, params).then(function(response) {
+ return response["rows"];
+ });
+ };
+
+ /**
+ * Performs a GET to the server and checks whether the "rows" array attribute of the response
+ * has the given value.
+ *
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param value
+ */
+ this.chainHasResponseRow = function(chainable, uri, value)
+ {
+ return this.chainGetResponse(chainable, uri).then(function(response) {
+ let authorized = false;
+ for (let i = 0; i < response.rows.length; i++)
+ {
+ if (response.rows[i].toLowerCase() === value.toLowerCase())
+ {
+ authorized = true;
+ }
+ }
+ return authorized;
+ });
+ };
+
+ /**
+ * Performs a POST to the server and pushes the response into the chain.
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ * @param payload
+ */
+ this.chainPostResponse = function(chainable, uri, params, payload)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ driver.gitanaPost(uri, params, payload, function(response) {
+ chain.next(response);
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+
+ /**
+ * Helper to gets the principal id for a principal object, json structure or principal id itself.
+ * This returns something like "domainId/principalId"
+ *
+ * @param principal
+ * @param defaultDomainId
+ */
+ this.extractPrincipalDomainQualifiedId = function(principal, defaultDomainId)
+ {
+ const identifiers = this.extractPrincipalIdentifiers(principal, defaultDomainId);
+
+ return identifiers["domain"] + "/" + identifiers["principal"];
+ };
+
+ /**
+ * Helper to gets the principal id for a principal object, json structure or principal id itself.
+ * This returns something like "domainId/principalId"
+ *
+ * @param principal principal object or string (principal id or domain qualified principal id)
+ * @param defaultDomainId
+ */
+ this.extractPrincipalIdentifiers = function(principal, defaultDomainId)
+ {
+ const identifiers = {};
+
+ if (!defaultDomainId)
+ {
+ defaultDomainId = "default";
+ }
+
+ if (Gitana.isString(principal))
+ {
+ const x = principal.indexOf("/");
+ if (x > -1)
+ {
+ identifiers["domain"] = principal.substring(0, x);
+ identifiers["principal"] = principal.substring(x + 1);
+ }
+ else
+ {
+ identifiers["domain"] = defaultDomainId;
+ identifiers["principal"] = principal;
+ }
+ }
+ else if (principal.objectType && principal.objectType() === "Gitana.DomainPrincipal")
+ {
+ identifiers["domain"] = principal.getDomainId();
+ identifiers["principal"] = principal.getId();
+ }
+ else if (principal.objectType && principal.objectType() === "Gitana.TeamMember")
+ {
+ identifiers["domain"] = principal["domainId"];
+ identifiers["principal"] = principal["_doc"];
+ }
+ else if (principal["_doc"])
+ {
+ identifiers["domain"] = defaultDomainId;
+ identifiers["principal"] = principal["_doc"];
+ }
+ else if (principal["name"])
+ {
+ identifiers["domain"] = defaultDomainId;
+ identifiers["principal"] = principal["name"];
+ }
+
+ return identifiers;
+ };
+ },
+
+ /**
+ * Used internally during chain copy going forward or backward through the chain.
+ *
+ * This is wired into the Chain.__copyState method and allows a convenient way to override
+ * chain copy behavior on a per-object-type basis.
+ *
+ * @param otherObject
+ */
+ chainCopyState: function(otherObject)
+ {
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.Response = Base.extend(
+ /** @lends Gitana.Response.prototype */
+ {
+ /**
+ * @constructs
+ *
+ * @class Gitana Response that wraps a response document from the Gitana server.
+ *
+ * @param {Object} object json response object
+ */
+ constructor: function(object)
+ {
+ Gitana.copyInto(this, object);
+ },
+
+ /**
+ * Gets the id ("_doc") field of the response (if one is available).
+ *
+ * @public
+ *
+ * @returns {String} id
+ */
+ getId: function()
+ {
+ return this["_doc"];
+ },
+
+ /**
+ * Indicates whether this response is a Status Document.
+ *
+ * @public
+ *
+ * @returns {Boolean} whether this is a status document
+ */
+ isStatusDocument: function()
+ {
+ return (this["ok"] || this["error"]);
+ },
+
+ /**
+ * Indicates whether this response is a List Document.
+ *
+ * @public
+ *
+ * @returns {Boolean} whether this is a list document
+ */
+ isListDocument: function()
+ {
+ return this["total_rows"] && this["rows"] && this["offset"];
+ },
+
+ /**
+ * Indicates whether this response is a Data Document.
+ *
+ * @public
+ *
+ * @returns {Boolean} whether this is a data document
+ */
+ isDataDocument: function()
+ {
+ return (!this.isStatusDocument() && !this.isListDocument());
+ },
+
+ /**
+ * Indicates whether the response is "ok".
+ *
+ * @public
+ *
+ * @returns {Boolean} whether the response is "ok"
+ */
+ isOk: function()
+ {
+ // assume things are ok
+ let ok = true;
+
+ if (this.isStatusDocument()) {
+ if (this["ok"] != null) {
+ ok = this["ok"];
+ }
+ }
+
+ // any document type can specify an error
+ if (this["error"]) {
+ ok = false;
+ }
+
+ return ok;
+ },
+
+ /**
+ * Indicates whetehr the response is in an error state.
+ *
+ * @public
+ *
+ * @returns {Boolean} whether the response is in an error state
+ */
+ isError: function()
+ {
+ return !this.isOk();
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.AuthInfo = Base.extend(
+ /** @lends Gitana.AuthInfo.prototype */
+ {
+ /**
+ * @constructs
+ *
+ * @class Gitana.AuthInfo holds authentication info for the driver
+ *
+ * @param {Object} object json response object
+ */
+ constructor: function(object)
+ {
+ Gitana.copyInto(this, object);
+ },
+
+ getPrincipalId: function()
+ {
+ return this["principalId"];
+ },
+
+ getPrincipalDomainId: function()
+ {
+ return this["principalDomainId"];
+ },
+
+ getPrincipalName: function()
+ {
+ return this["principalName"];
+ },
+
+ getTenantId: function()
+ {
+ return this["tenantId"];
+ },
+
+ getTenantTitle: function()
+ {
+ return this["tenantTitle"];
+ },
+
+ getTenantDescription: function()
+ {
+ return this["tenantDescription"];
+ },
+
+ getClientId: function()
+ {
+ return this["clientId"];
+ },
+
+ getTicket: function()
+ {
+ return this["ticket"];
+ }
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.SystemMetadata = Base.extend(
+ /** @lends Gitana.SystemMetadata.prototype */
+ {
+ /**
+ * @constructs
+ *
+ * @class System Metadata
+ */
+ constructor: function()
+ {
+ this.base();
+ },
+
+ updateFrom: function(json)
+ {
+ // clear old system properties
+ Gitana.deleteProperties(this, false);
+
+ Gitana.copyInto(this, json);
+ },
+
+ get: function(key)
+ {
+ return this[key];
+ },
+
+ /**
+ * Retrieves the changeset id.
+ *
+ * @public
+ *
+ * @return the changeset id
+ */
+ getChangesetId: function()
+ {
+ return this.get("changeset");
+ },
+
+ /**
+ * Retrieves the name of the user who created this object.
+ *
+ * @public
+ *
+ * @return the user name of the creator
+ */
+ getCreatedBy: function()
+ {
+ return this.get("created_by");
+ },
+
+ /**
+ * Retrieves the id of the user who created this object.
+ *
+ * @public
+ *
+ * @return the user id of the creator
+ */
+ getCreatedByPrincipalId: function()
+ {
+ return this.get("created_by_principal_id");
+ },
+
+ /**
+ * Retrieves the domain id of the user who created this object.
+ *
+ * @public
+ *
+ * @return the user domain id of the creator
+ */
+ getCreatedByPrincipalDomainId: function()
+ {
+ return this.get("created_by_principal_domain_id");
+ },
+
+ /*
+ readCreatedByPrincipal: function(platform)
+ {
+ return this.subchain(platform).readDomain(this.getCreatedByPrincipalDomainId).readPrincipal(this.getCreatedByPrincipalId);
+ },
+ */
+
+ /**
+ * Retrieves the id of the user who modified this object.
+ *
+ * @public
+ *
+ * @return the user id of the modifier
+ */
+ getModifiedBy: function()
+ {
+ return this.get("modified_by");
+ },
+
+ /**
+ * Retrieves the id of the user who modified this object.
+ *
+ * @public
+ *
+ * @return the user id of the modifier
+ */
+ getModifiedByPrincipalId: function()
+ {
+ return this.get("modified_by_principal_id");
+ },
+
+ /**
+ * Retrieves the domain id of the user who modified this object.
+ *
+ * @public
+ *
+ * @return the user domain id of the modifier
+ */
+ getModifiedByPrincipalDomainId: function()
+ {
+ return this.get("modified_by_principal_domain_id");
+ },
+
+ /*
+ readModifiedByPrincipal: function(platform)
+ {
+ return this.subchain(platform).readDomain(this.getModifiedByPrincipalDomainId).readPrincipal(this.getModifiedByPrincipalId);
+ },
+ */
+
+ /**
+ * Retrieves the timestamp for creation of this object.
+ *
+ * @public
+ *
+ * @return creation timestamp
+ */
+ getCreatedOn: function()
+ {
+ if (!this.createdOn)
+ {
+ this.createdOn = new Gitana.Timestamp(this.get("created_on"));
+ }
+
+ return this.createdOn;
+ },
+
+ /**
+ * Retrieves the timestamp for modification of this object.
+ *
+ * @public
+ *
+ * @return modification timestamp
+ */
+ getModifiedOn: function()
+ {
+ if (!this.modifiedOn)
+ {
+ this.modifiedOn = new Gitana.Timestamp(this.get("modified_on"));
+ }
+
+ return this.modifiedOn;
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.Timestamp = Base.extend(
+ /** @lends Gitana.Timestamp.prototype */
+ {
+ /**
+ * @constructs
+ *
+ * @class Timestamp
+ *
+ * @param {Object} object the timestamp json object
+ */
+ constructor: function(object)
+ {
+ this.base(object);
+ },
+
+ /**
+ * @returns {number} the year
+ */
+ getYear: function()
+ {
+ return this["year"];
+ },
+
+ /**
+ @returns {number} the month
+ */
+ getMonth: function()
+ {
+ return this["month"];
+ },
+
+ /**
+ * @returns {number} the day of the month
+ */
+ getDay: function()
+ {
+ return this["day_of_month"];
+ },
+
+ /**
+ * @returns {number} the hour of the day (24 hour clock)
+ */
+ getHour: function()
+ {
+ return this["hour"];
+ },
+
+ /**
+ * @returns {number} the minute
+ */
+ getMinute: function()
+ {
+ return this["minute"];
+ },
+
+ /**
+ * @returns {number} the second
+ */
+ getSecond: function()
+ {
+ return this["second"];
+ },
+
+ /**
+ * @returns {number} the millisecond (0-1000)
+ */
+ getMillisecond: function()
+ {
+ return this["millisecond"];
+ },
+
+ /**
+ * @returns {number} absolute millisecond
+ */
+ getTime: function()
+ {
+ return this["ms"];
+ },
+
+ /**
+ * @returns {String} text-friendly timestamp
+ */
+ getTimestamp: function()
+ {
+ return this["timestamp"];
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana.uniqueIdCounter = 0;
+
+ /**
+ * Builds an array from javascript method arguments.
+ *
+ * @inner
+ *
+ * @param {arguments} args
+ *
+ * @returns {Array} an array
+ */
+ Gitana.makeArray = function(args) {
+ return Array.prototype.slice.call(args);
+ };
+
+ /**
+ * Serializes a object into a JSON string and optionally makes it pretty by indenting.
+ *
+ * @inner
+ *
+ * @param {Object} object The javascript object.
+ * @param {Boolean} pretty Whether the resulting string should have indentation.
+ *
+ * @returns {String} string
+ */
+ Gitana.stringify = function(object, pretty) {
+
+ let val = null;
+
+ if (!Gitana.isEmpty(object))
+ {
+ if (pretty)
+ {
+ val = JSON.stringify(object, null, " ");
+ }
+ else
+ {
+ val = JSON.stringify(object);
+ }
+ }
+
+ return val;
+ };
+
+ /**
+ * Determines whether the given argument is a String.
+ *
+ * @inner
+ *
+ * @param arg argument
+ *
+ * @returns {Boolean} whether it is a String
+ */
+ Gitana.isString = function( arg ) {
+ return (typeof arg === "string");
+ };
+
+ /**
+ * Determines whether the given argument is a Number.
+ *
+ * @inner
+ *
+ * @param arg argument
+ *
+ * @returns {Boolean} whether it is a Number
+ */
+ Gitana.isNumber = function( arg ) {
+ return (typeof arg === "number");
+ };
+
+ /**
+ * Determines whether the given argument is a Boolean.
+ *
+ * @inner
+ *
+ * @param arg argument
+ *
+ * @returns {Boolean} whether it is a Boolean
+ */
+ Gitana.isBoolean = function( arg ) {
+ return (typeof arg === "boolean");
+ };
+
+ /**
+ * Determines whether the given argument is a Function.
+ *
+ * @inner
+ *
+ * @param arg argument
+ *
+ * @returns {Boolean} whether it is a Function
+ */
+ Gitana.isFunction = function(arg) {
+ return Object.prototype.toString.call(arg) === "[object Function]";
+ };
+
+ /**
+ * Determines whether the given argument is an Object.
+ *
+ * @param obj
+ *
+ * @returns {boolean} whether it is an Object
+ */
+ Gitana.isObject = function(obj) {
+ return !Gitana.isUndefined(obj) && Object.prototype.toString.call(obj) === '[object Object]';
+ };
+
+ /**
+ * Determines whether a bit of text starts with a given prefix.
+ *
+ * @inner
+ *
+ * @param {String} text A bit of text.
+ * @param {String} prefix The prefix.
+ *
+ * @returns {Boolean} whether the text starts with the prefix.
+ */
+ Gitana.startsWith = function(text, prefix) {
+ return text.substr(0, prefix.length) === prefix;
+ };
+
+ /**
+ * Copies the members of the source object into the target object.
+ * This includes both properties and functions from the source object.
+ *
+ * @inner
+ *
+ * @param {Object} target Target object.
+ * @param {Object} source Source object.
+ */
+ Gitana.copyInto = function(target, source) {
+ for (let i in source) {
+ if (source.hasOwnProperty(i) && !this.isFunction(source[i])) {
+ target[i] = source[i];
+ }
+ }
+ };
+
+ /**
+ * Deletes any owned properties of the given object. If specified, owned functions will also be deleted.
+ *
+ * @inner
+ *
+ * @param object {Object} object
+ * @param deleteFunctions
+ */
+ Gitana.deleteProperties = function(object, deleteFunctions) {
+ const keys = [];
+ for (let k in object) { keys.push(k); }
+
+ for (let i = 0; i < keys.length; i++)
+ {
+ const key = keys[i];
+
+ if (object.hasOwnProperty(key)) {
+ if (!Gitana.isFunction(object[key]) || (deleteFunctions && Gitana.isFunction(object[key]))) {
+ delete object[key];
+ }
+ }
+ }
+ };
+
+
+ /**
+ * Stamps the functions and properties from the source object to the target object.
+ *
+ * @inner
+ *
+ * @param {Object} target Target object.
+ * @param {Object} source Source object.
+ */
+ Gitana.stampInto = function(target, source) {
+ for (let i in source)
+ {
+ if (source.hasOwnProperty(i))
+ {
+ target[i] = source[i];
+ }
+ }
+ };
+
+ Gitana.contains = function(a, obj)
+ {
+ let i = a.length;
+ while (i--)
+ {
+ if (a[i] === obj)
+ {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ Gitana.isArray = function(obj)
+ {
+ return obj.push && obj.slice;
+ };
+
+ Gitana.isUndefined = function(obj)
+ {
+ return (typeof obj === "undefined");
+ };
+
+ Gitana.isEmpty = function(obj)
+ {
+ return this.isUndefined(obj) || obj === null;
+ };
+
+ Gitana.generateId = function()
+ {
+ Gitana.uniqueIdCounter++;
+ return "gitana-" + Gitana.uniqueIdCounter;
+ };
+
+ Gitana.isNode = function(o)
+ {
+ return (
+ typeof Node === "object" ? o instanceof Node :
+ typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string");
+ };
+
+ Gitana.isElement = function(o)
+ {
+ return (
+ typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
+ typeof o === "object" && o.nodeType === 1 && typeof o.nodeName==="string");
+ };
+
+ Gitana.debug = function(str)
+ {
+ if (!this.isUndefined(console))
+ {
+ console.log(str);
+ }
+ };
+
+ Gitana.error = function(str)
+ {
+ if (!this.isUndefined(console))
+ {
+ console.error(str);
+ }
+ };
+
+ Gitana.getNumberOfKeys = function(map)
+ {
+ let count = 0;
+ for (let key in map) {
+ count++;
+ }
+
+ return count;
+ };
+
+ /**
+ * Writes a cookie.
+ *
+ * @param {String} name
+ * @param {String} value
+ * @param {String} path optional path (assumed "/" if not provided)
+ * @param {Number} days optional # of days to store cookie
+ * if null or -1, assume session cookie
+ * if 0, assume expired cookie
+ * if > 0, assume # of days
+ * @param {String} domain optional domain (otherwise assumes wildcard base domain)
+ */
+ Gitana.writeCookie = function(name, value, path, days, domain)
+ {
+ if (typeof(document) !== "undefined")
+ {
+ const createCookie = function(name, value, path, days, host)
+ {
+ // path
+ if (!path)
+ {
+ path = "/";
+ }
+ const pathString = ";path=" + path;
+
+ // expiration
+ let expirationString = "";
+ if (typeof(days) === "undefined" || days === -1)
+ {
+ // session cookie
+ }
+ else if (days === 0)
+ {
+ // expired cookie
+ expirationString = ";expires=Thu, 01 Jan 1970 00:00:01 GMT";
+ }
+ else if (days > 0)
+ {
+ const date = new Date();
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expirationString = ";expires="+date.toGMTString();
+ }
+
+ // domain
+ let domainString = "";
+ if (host)
+ {
+ domainString = ";domain=" + host;
+ }
+
+ document.cookie = name + "=" + value + expirationString + pathString + domainString + ";";
+ };
+
+ createCookie(name, value, path, days, domain);
+ }
+ };
+
+ /**
+ * Deletes a cookie.
+ *
+ * @param name
+ * @param path
+ */
+ Gitana.deleteCookie = function(name, path)
+ {
+ const existsCookie = function(name)
+ {
+ return Gitana.readCookie(name);
+ };
+
+ if (typeof(document) != "undefined")
+ {
+ // first attempt, let the browser sort out the assumed domain
+ // this works for most modern browsers
+ if (existsCookie(name))
+ {
+ // use expiration time of 0 to signal expired cookie
+ Gitana.writeCookie(name, "", path, 0);
+ }
+
+ // second attempt, if necessary, plug in an assumed domain
+ // this is needed for phantomjs
+ if (existsCookie(name))
+ {
+ // see if we can resolve a domain
+ if (window)
+ {
+ let domain = window.location.host;
+ if (domain)
+ {
+ // remove :port
+ const i = domain.indexOf(":");
+ if (i > -1)
+ {
+ domain = domain.substring(0, i);
+ }
+ }
+
+ // use expiration time of 0 to signal expired cookie
+ Gitana.writeCookie(name, "", path, 0, domain);
+ }
+ }
+ }
+ };
+
+ /**
+ *
+ * @param name
+ * @return {null}
+ */
+ Gitana.readCookie = function(name)
+ {
+ function _readCookie(name)
+ {
+ const nameEQ = name + "=";
+ const ca = document.cookie.split(';');
+ for (let i = 0; i < ca.length; i++)
+ {
+ let c = ca[i];
+ while (c.charAt(0) ===' ')
+ {
+ c = c.substring(1,c.length);
+ }
+
+ if (c.indexOf(nameEQ) === 0)
+ {
+ return c.substring(nameEQ.length,c.length);
+ }
+ }
+ return null;
+ }
+
+ let value = null;
+
+ if (typeof(document) !== "undefined")
+ {
+ value = _readCookie(name);
+ }
+
+ return value;
+ };
+
+
+ /**
+ *
+ * @param paramName
+ * @return {string|null}
+ */
+ Gitana.getCurrentQueryStringParameter = function(paramName)
+ {
+ let searchString = window.location.search.substring(1), i, val, params = searchString.split("&");
+
+ for (i = 0; i < params.length; i++)
+ {
+ val = params[i].split("=");
+
+ if (val[0] === paramName)
+ {
+ return unescape(val[1]);
+ }
+ }
+
+ return null;
+ };
+
+ /**
+ *
+ * @param paramName
+ * @return {string|null}
+ */
+ Gitana.getCurrentHashStringParameter = function(paramName)
+ {
+ const searchString = window.location.href.substring(window.location.href.indexOf("#") + 1);
+ const params = searchString.split("&");
+
+ for (i = 0; i < params.length; i++)
+ {
+ val = params[i].split("=");
+
+ if (val[0] === paramName)
+ {
+ return unescape(val[1]);
+ }
+ }
+
+ return null;
+ };
+
+ /**
+ *
+ * @param string
+ * @return {string|string}
+ */
+ Gitana.btoa = function(string)
+ {
+ const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+ let i = 0, length = string.length, ascii, index, output = '';
+
+ for (; i < length; i+=3) {
+ ascii = [
+ string.charCodeAt(i),
+ string.charCodeAt(i+1),
+ string.charCodeAt(i+2)
+ ];
+
+ index = [
+ ascii[0] >> 2,
+ ((ascii[0] & 3) << 4) | ascii[1] >> 4,
+ ((ascii[1] & 15) << 2) | ascii[2] >> 6,
+ ascii[2] & 63
+ ];
+
+ if (isNaN(ascii[1])) {
+ index[2] = 64;
+ }
+ if (isNaN(ascii[2])) {
+ index[3] = 64;
+ }
+
+ output += b64.charAt(index[0]) + b64.charAt(index[1]) + b64.charAt(index[2]) + b64.charAt(index[3]);
+ }
+
+ return output;
+ };
+
+ /**
+ * Copies only those members that are already represented on the target.
+ *
+ * @inner
+ *
+ * @param {Object} target Target object.
+ * @param {Object} source Source object.
+ */
+ Gitana.copyKeepers = function(target, source) {
+
+ if (!source) { return; }
+
+ for (let i in source) {
+ if (source.hasOwnProperty(i) && !this.isFunction(source[i])) {
+ if (!Gitana.isUndefined(target[i])) {
+ target[i] = source[i];
+ }
+ }
+ }
+ };
+
+})(window);(function(window) {
+
+ Gitana = window.Gitana;
+
+ const STATUS_UNRESOLVED = 'unresolved';
+ const STATUS_RESOLVED = 'resolved';
+ const STATUS_REJECTED = 'rejected';
+
+ const triggerAll = function(val, cbs) {
+ for (let i = 0; i < cbs.length; i++) {
+ const cb = cbs[i];
+ trigger(val, cb);
+ }
+ };
+
+ const trigger = function(val, cb) {
+ setTimeout(cb.bind(null, val), 0);
+ };
+
+ const resolve = function(val) {
+ if (this.isUnresolved()) {
+ this.status = STATUS_RESOLVED;
+ this.val = val;
+ triggerAll(val, this.successCallbacks);
+ delete this.successCallbacks;
+ delete this.errorCallbacks;
+ }
+ };
+
+ const reject = function(err) {
+ if (this.isUnresolved()) {
+ this.status = STATUS_REJECTED;
+ this.val = err;
+ triggerAll(err, this.errorCallbacks);
+ delete this.successCallbacks;
+ delete this.errorCallbacks;
+ }
+ };
+
+ const Defer = function() {
+ this.promise = new Gitana.Promise(this);
+
+ this.status = STATUS_UNRESOLVED;
+
+ this.successCallbacks = [];
+ this.errorCallbacks = [];
+
+ this.resolve = resolve.bind(this);
+ this.reject = reject.bind(this);
+ };
+
+ Defer.prototype.push = function(happy, sad) {
+ if (this.isUnresolved()) {
+ if (typeof happy === 'function') { this.successCallbacks.push(happy); }
+ if (typeof sad === 'function') { this.errorCallbacks.push(sad); }
+ } else if (this.isResolved()) {
+ trigger(this.val, happy);
+ } else if (this.isRejected()) {
+ trigger(this.val, sad);
+ }
+ };
+
+ Defer.prototype.isUnresolved = function() {
+ return this.status === STATUS_UNRESOLVED;
+ };
+
+ Defer.prototype.isResolved = function() {
+ return this.status === STATUS_RESOLVED;
+ };
+
+ Defer.prototype.isRejected = function() {
+ return this.status === STATUS_REJECTED;
+ };
+
+ Defer.all = function(args) {
+ if (args === undefined) {
+ return Gitana.Promise.resolved();
+ }
+ if (!Gitana.isArray(args)) { args = arguments; }
+ const def = new Defer();
+ let left = args.length;
+ const results = [];
+ for (let i = 0; i < args.length; i++) {
+ const promise = args[i];
+ (function(cur) {
+ promise.then(function(res) {
+ left--;
+ results[cur] = res;
+ if (left <= 0) {
+ def.resolve(results);
+ }
+ }, def.reject);
+ })(i);
+ }
+ return def.promise;
+ };
+
+ Gitana.Defer = Defer;
+
+})(window);
+(function(window) {
+
+ Gitana = window.Gitana;
+
+ const then = function(happy, sad) {
+ this.push(happy, sad);
+ };
+
+ const success = function(happy) {
+ then.call(this, happy);
+ };
+
+ const fail = function(sad) {
+ then.call(this, undefined, sad);
+ };
+
+ const complete = function(cb) {
+ then.call(this, cb, cb);
+ };
+
+ const Promise = function(defer) {
+
+ this.then = then.bind(defer);
+ this.success = success.bind(defer);
+ this.fail = fail.bind(defer);
+ this.complete = complete.bind(defer);
+
+ this.status = function() {
+ return defer.status;
+ };
+
+ };
+
+ Promise.resolved = function(val) {
+ const def = new Gitana.Defer();
+ def.resolve(val);
+ return def.promise;
+ };
+
+ Gitana.Promise = Promise;
+
+})(window);
+(function(window) {
+
+ Gitana = window.Gitana;
+
+ const DEFAULT_CONCURRENCY = 1;
+
+ const chunk = function(array, size) {
+ const chunks = [];
+ for (let i = 0; i < array.length; i += size) {
+ chunks.push(array.slice(i, i + size));
+ }
+ return chunks;
+ };
+
+ const Queue = function(concurrency) {
+ this.concurrency = concurrency || DEFAULT_CONCURRENCY;
+ this.work = [];
+ };
+
+ Queue.prototype.add = function(fn) {
+ this.work.push(fn);
+ };
+
+ Queue.prototype.go = function() {
+ const def = new Gitana.Defer();
+ const chunks = chunk(this.work, this.concurrency);
+ const results = [];
+ (function loop(promise) {
+ promise.then(function(res) {
+ results.push.apply(results, res);
+ if (chunks.length > 0) {
+ const cbs = chunks.shift();
+ const ps = [];
+ for (let i = cbs.length - 1; i >= 0; i--) {
+ const cb = cbs[i];
+ const p = cb();
+ ps.push(p);
+ }
+ loop(Gitana.Defer.all(ps));
+ } else {
+ def.resolve(results);
+ }
+ }, def.reject);
+ })(Gitana.Promise.resolved([]));
+ return def.promise;
+ };
+
+ Gitana.Queue = Queue;
+
+})(window);
+(function(window)
+{
+ Gitana.Methods = {};
+
+ /**
+ * Produces the common function to handle listAttachments() on constious attachables within Gitana.
+ *
+ * @param {Function} [mapClass] map implementation class (if none provided, uses Gitana.BinaryAttachmentMap)
+ *
+ * @return {Function}
+ */
+ Gitana.Methods.listAttachments = function(mapClass) {
+
+ if (!mapClass) {
+ mapClass = Gitana.BinaryAttachmentMap;
+ }
+
+ return function(local) {
+
+ const self = this;
+
+ const result = this.subchain(new mapClass(this));
+ if (!local)
+ {
+ // front-load some work that fetches from remote server
+ result.then(function() {
+
+ const chain = this;
+
+ self.getDriver().gitanaGet(self.getUri() + "/attachments", null, {}, function(response) {
+ chain.handleResponse(response);
+ chain.next();
+ });
+
+ return false;
+ });
+ }
+ else
+ {
+ // try to populate the map from our cached values on the node (if they exist)
+ const existingMap = self.getSystemMetadata()["attachments"];
+ if (existingMap)
+ {
+ // attachments that come off of system() don't have "attachmentId" on their json object
+ // instead, the "attachmentId" is the key into the map.
+
+ // so here, in terms of normalizing things, we copy "attachmentId" into the json object
+ for (const key in existingMap)
+ {
+ const value = result[key];
+ value["attachmentId"] = key;
+ }
+ }
+
+ //result.handleResponse(existingMap);
+ }
+
+ return result;
+ };
+ };
+
+ /**
+ * Produces the common function to handle attach() of attachments to an attachable within Gitana.
+ *
+ * @param [attachmentClass] attachment implementation class (if none provided, uses Gitana.BinaryAttachment)
+ * @param [paramsFunction] optional population function for url params
+ *
+ * @return {Function}
+ */
+ Gitana.Methods.attach = function(attachmentClass, paramsFunction) {
+
+ if (!attachmentClass) {
+ attachmentClass = Gitana.BinaryAttachment;
+ }
+
+ return function(attachmentId, contentType, data)
+ {
+ const self = this;
+
+ if (!attachmentId)
+ {
+ attachmentId = "default";
+ }
+
+ // the thing we're handing back
+ const result = this.subchain(new attachmentClass(this));
+
+ // preload some work onto a subchain
+ result.then(function() {
+
+ // params
+ const params = {};
+ if (paramsFunction) {
+ paramsFunction(params);
+ }
+
+ // upload the attachment
+ let uploadUri = self.getUri() + "/attachments/" + attachmentId;
+
+ // if data is a Node read stream, we use a helper function possibly to conduct the upload
+ if (data && data.read && typeof(data.read) === "function" && Gitana.streamUpload)
+ {
+ this.subchain(self).then(function() {
+
+ const chain = this;
+
+ uploadUri = self.getDriver().baseURL + uploadUri;
+ Gitana.streamUpload(self.getDriver(), data, uploadUri, contentType, function() {
+
+ // read back attachment information and plug onto result
+ Chain(self).reload().then(function() {
+ this.listAttachments().then(function() {
+ this.select(attachmentId).then(function () {
+ result.handleResponse(this);
+ chain.next();
+ });
+ });
+ });
+ });
+
+ return false;
+ });
+ }
+ else
+ {
+ this.chainUpload(this, uploadUri, params, contentType, data).then(function () {
+
+ // read back attachment information and plug onto result
+ this.subchain(self).listAttachments().then(function () {
+
+ // TODO: update attachment information on attachable.system() ?
+
+ this.select(attachmentId).then(function () {
+ result.handleResponse(this);
+ });
+ });
+ });
+ }
+ });
+
+ return result;
+ };
+ };
+
+ /**
+ * Produces the common function to handle unattach() of attachments from an attachable within Gitana.
+ *
+ * @return {Function}
+ */
+ Gitana.Methods.unattach = function()
+ {
+ return function(attachmentId) {
+
+ return this.then(function() {
+ this.chainDelete(this, this.getUri() + "/attachments/" + attachmentId).then(function() {
+ // TODO
+ });
+ });
+ };
+ };
+
+ Gitana.Methods.getPreviewUri = function(prefix)
+ {
+ if (!prefix) {
+ prefix = "preview";
+ }
+
+ return function(name, config) {
+
+ let url = this.getDriver().baseURL + this.getUri() + "/" + prefix + "/" + name;
+
+ if (config)
+ {
+ let first = true;
+
+ for (const key in config)
+ {
+ if (first)
+ {
+ url += "?";
+ }
+ else
+ {
+ url += "&";
+ }
+
+ const value = config[key];
+ if (value)
+ {
+ url += key + "=" + value;
+ }
+
+ first = false;
+ }
+ }
+
+ return url;
+ };
+ };
+
+})(window);(function(window)
+{
+ // if we're running on the Cloud CMS hosted platform, we can auto-acquire the client key that we should use
+ (function() {
+
+ // check to make sure location exists (only available in browsers)
+ if (typeof window.location != "undefined")
+ {
+ if (typeof(Gitana.autoConfigUri) === "undefined")
+ {
+ let uri = window.location.href;
+ let z1 = uri.indexOf(window.location.pathname);
+ z1 = uri.indexOf("/", z1 + 2);
+ if (z1 > -1)
+ {
+ uri = uri.substring(0, z1);
+ }
+
+ if (uri.indexOf("cloudcms.net") > -1)
+ {
+ Gitana.autoConfigUri = uri;
+ }
+ }
+ }
+
+ }());
+
+})(window);(function(window)
+{
+ Gitana = window.Gitana;
+
+ /**
+ * Object factory
+ *
+ * Produces object instances (nodes included) for given json.
+ */
+ Gitana.ObjectFactory = Base.extend(
+ /** @lends Gitana.ObjectFactory.prototype */
+ {
+ constructor: function()
+ {
+ this.create = function(klass, existing, object)
+ {
+ return new klass(existing, object);
+ };
+ },
+
+ platformDataStoreMap: function(platform, object)
+ {
+ return this.create(Gitana.PlatformDataStoreMap, platform, object);
+ },
+
+ platformDataStore: function(platform, object)
+ {
+ const type = object.datastoreTypeId;
+
+ return this[type](platform, object);
+ },
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CLUSTER
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ platform: function(cluster, object)
+ {
+ return this.create(Gitana.Platform, cluster, object);
+ },
+
+ job: function(cluster, object)
+ {
+ let type = null;
+
+ if (object)
+ {
+ if (Gitana.isString(object))
+ {
+ type = object;
+ }
+ else
+ {
+ type = object["type"];
+ }
+ }
+
+ let job = null;
+ if ("copy" === type)
+ {
+ job = this.create(Gitana.CopyJob, cluster, object);
+ }
+ else if ("export" === type)
+ {
+ job = this.create(Gitana.TransferExportJob, cluster, object);
+ }
+ else if ("import" === type)
+ {
+ job = this.create(Gitana.TransferImportJob, cluster, object);
+ }
+ else
+ {
+ job = this.create(Gitana.Job, cluster, object);
+ }
+
+ return job;
+ },
+
+ jobMap: function(cluster, object)
+ {
+ return this.create(Gitana.JobMap, cluster, object);
+ },
+
+ logEntry: function(cluster, object)
+ {
+ return this.create(Gitana.LogEntry, cluster, object);
+ },
+
+ logEntryMap: function(cluster, object)
+ {
+ return this.create(Gitana.LogEntryMap, cluster, object);
+ },
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // PLATFORM
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ stack: function(platform, object)
+ {
+ return this.create(Gitana.Stack, platform, object);
+ },
+
+ stackMap: function(platform, object)
+ {
+ return this.create(Gitana.StackMap, platform, object);
+ },
+
+ project: function(platform, object)
+ {
+ return this.create(Gitana.Project, platform, object);
+ },
+
+ projectMap: function(platform, object)
+ {
+ return this.create(Gitana.ProjectMap, platform, object);
+ },
+
+ uiConfig: function(platform, object)
+ {
+ return this.create(Gitana.UIConfig, platform, object);
+ },
+
+ uiConfigMap: function(platform, object)
+ {
+ return this.create(Gitana.UIConfigMap, platform, object);
+ },
+
+ scheduledWork: function(platform, object)
+ {
+ return this.create(Gitana.ScheduledWork, platform, object);
+ },
+
+ scheduledWorkMap: function(platform, object)
+ {
+ return this.create(Gitana.ScheduledWorkMap, platform, object);
+ },
+
+ report: function(platform, object)
+ {
+ return this.create(Gitana.Report, platform, object);
+ },
+
+ reportMap: function(platform, object)
+ {
+ return this.create(Gitana.ReportMap, platform, object);
+ },
+
+ repository: function(platform, object)
+ {
+ return this.create(Gitana.Repository, platform, object);
+ },
+
+ repositoryMap: function(platform, object)
+ {
+ return this.create(Gitana.RepositoryMap, platform, object);
+ },
+
+ domain: function(platform, object)
+ {
+ return this.create(Gitana.Domain, platform, object);
+ },
+
+ domainMap: function(platform, object)
+ {
+ return this.create(Gitana.DomainMap, platform, object);
+ },
+
+ vault: function(platform, object)
+ {
+ return this.create(Gitana.Vault, platform, object);
+ },
+
+ vaultMap: function(platform, object)
+ {
+ return this.create(Gitana.VaultMap, platform, object);
+ },
+
+ registrar: function(platform, object)
+ {
+ return this.create(Gitana.Registrar, platform, object);
+ },
+
+ registrarMap: function(platform, object)
+ {
+ return this.create(Gitana.RegistrarMap, platform, object);
+ },
+
+ directory: function(platform, object)
+ {
+ return this.create(Gitana.Directory, platform, object);
+ },
+
+ directoryMap: function(platform, object)
+ {
+ return this.create(Gitana.DirectoryMap, platform, object);
+ },
+
+ application: function(platform, object)
+ {
+ return this.create(Gitana.Application, platform, object);
+ },
+
+ applicationMap: function(platform, object)
+ {
+ return this.create(Gitana.ApplicationMap, platform, object);
+ },
+
+ webhost: function(platform, object)
+ {
+ return this.create(Gitana.WebHost, platform, object);
+ },
+
+ webhostMap: function(platform, object)
+ {
+ return this.create(Gitana.WebHostMap, platform, object);
+ },
+
+ autoClientMapping: function(webhost, object)
+ {
+ return this.create(Gitana.AutoClientMapping, webhost, object);
+ },
+
+ autoClientMappingMap: function(webhost, object)
+ {
+ return this.create(Gitana.AutoClientMappingMap, webhost, object);
+ },
+
+ trustedDomainMapping: function(webhost, object)
+ {
+ return this.create(Gitana.TrustedDomainMapping, webhost, object);
+ },
+
+ trustedDomainMappingMap: function(webhost, object)
+ {
+ return this.create(Gitana.TrustedDomainMappingMap, webhost, object);
+ },
+
+ deployedApplication: function(webhost, object)
+ {
+ return this.create(Gitana.DeployedApplication, webhost, object);
+ },
+
+ deployedApplicationMap: function(webhost, object)
+ {
+ return this.create(Gitana.DeployedApplicationMap, webhost, object);
+ },
+
+ descriptor: function(platform, object)
+ {
+ return this.create(Gitana.Descriptor, platform, object);
+ },
+
+ descriptorMap: function(platform, object)
+ {
+ return this.create(Gitana.DescriptorMap, platform, object);
+ },
+
+ client: function(platform, object)
+ {
+ const client = this.create(Gitana.Client, platform, object);
+ Gitana.stampInto(client, Gitana.ClientMethods);
+
+ return client;
+ },
+
+ clientMap: function(platform, object)
+ {
+ return this.create(Gitana.ClientMap, platform, object);
+ },
+
+ accessPolicy: function(platform, object)
+ {
+ return this.create(Gitana.AccessPolicy, platform, object);
+ },
+
+ accessPolicyMap: function(platform, object)
+ {
+ return this.create(Gitana.AccessPolicyMap, platform, object);
+ },
+
+ authenticationGrant: function(platform, object)
+ {
+ return this.create(Gitana.AuthenticationGrant, platform, object);
+ },
+
+ authenticationGrantMap: function(platform, object)
+ {
+ return this.create(Gitana.AuthenticationGrantMap, platform, object);
+ },
+
+ billingProviderConfiguration: function(platform, object)
+ {
+ return this.create(Gitana.BillingProviderConfiguration, platform, object);
+ },
+
+ billingProviderConfigurationMap: function(platform, object)
+ {
+ return this.create(Gitana.BillingProviderConfigurationMap, platform, object);
+ },
+
+ workflowModel: function(platform, object)
+ {
+ return this.create(Gitana.WorkflowModel, platform, object);
+ },
+
+ workflowModelMap: function(platform, object)
+ {
+ return this.create(Gitana.WorkflowModelMap, platform, object);
+ },
+
+ workflowInstance: function(platform, object)
+ {
+ return this.create(Gitana.WorkflowInstance, platform, object);
+ },
+
+ workflowInstanceMap: function(platform, object)
+ {
+ return this.create(Gitana.WorkflowInstanceMap, platform, object);
+ },
+
+ workflowTask: function(platform, object)
+ {
+ return this.create(Gitana.WorkflowTask, platform, object);
+ },
+
+ workflowTaskMap: function(platform, object)
+ {
+ return this.create(Gitana.WorkflowTaskMap, platform, object);
+ },
+
+ workflowComment: function(platform, object)
+ {
+ return this.create(Gitana.WorkflowComment, platform, object);
+ },
+
+ workflowCommentMap: function(platform, object)
+ {
+ return this.create(Gitana.WorkflowCommentMap, platform, object);
+ },
+
+ deploymentReceiver: function(platform, object)
+ {
+ return this.create(Gitana.DeploymentReceiver, platform, object);
+ },
+
+ deploymentReceiverMap: function(platform, object)
+ {
+ return this.create(Gitana.DeploymentReceiverMap, platform, object);
+ },
+
+ deploymentPackage: function(platform, object)
+ {
+ return this.create(Gitana.DeploymentPackage, platform, object);
+ },
+
+ deploymentPackageMap: function(platform, object)
+ {
+ return this.create(Gitana.DeploymentPackageMap, platform, object);
+ },
+
+ deploymentStrategy: function(platform, object)
+ {
+ return this.create(Gitana.DeploymentStrategy, platform, object);
+ },
+
+ deploymentStrategyMap: function(platform, object)
+ {
+ return this.create(Gitana.DeploymentStrategyMap, platform, object);
+ },
+
+ deploymentTarget: function(platform, object)
+ {
+ return this.create(Gitana.DeploymentTarget, platform, object);
+ },
+
+ deploymentTargetMap: function(platform, object)
+ {
+ return this.create(Gitana.DeploymentTargetMap, platform, object);
+ },
+
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // REPOSITORY
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ changeset: function(repository, object)
+ {
+ return this.create(Gitana.Changeset, repository, object);
+ },
+
+ branch: function(repository, object)
+ {
+ return this.create(Gitana.Branch, repository, object);
+ },
+
+ /**
+ * Creates a node
+ *
+ * @param branch
+ * @param object either object or the string type id
+ */
+ node: function(branch, object)
+ {
+ let objectClass = null;
+
+ if (object)
+ {
+ // allow for object to be the type id
+ if (Gitana.isString(object))
+ {
+ object = {
+ "_type": object
+ };
+ }
+
+ // see if we can derive a more accurate type
+ const type = object["_type"];
+ if (type)
+ {
+ if (Gitana.ObjectFactory.registry[type])
+ {
+ objectClass = Gitana.ObjectFactory.registry[type];
+ }
+ }
+ if (!objectClass)
+ {
+ // allow default trip through to association for association types
+ if (type && Gitana.startsWith(type, "a:"))
+ {
+ objectClass = Gitana.Association;
+ }
+ }
+ if (!objectClass)
+ {
+ // check out if it says its an association via special key
+ if (object.__is_association && object.__is_association())
+ {
+ objectClass = Gitana.Association;
+ }
+ }
+ }
+ if (!objectClass)
+ {
+ // assume node
+ objectClass = Gitana.Node;
+ }
+
+ // instantiate and set any properties
+ return this.create(objectClass, branch, object);
+ },
+
+ association: function(branch, object)
+ {
+ return this.create(Gitana.Association, branch, object);
+ },
+
+ release: function(repository, object)
+ {
+ return this.create(Gitana.Release, repository, object);
+ },
+
+ mergeConflict: function(repository, object)
+ {
+ return this.create(Gitana.MergeConflict, repository, object);
+ },
+
+ deletion: function(branch, object)
+ {
+ return this.create(Gitana.Deletion, branch, object);
+ },
+
+ branchMap: function(repository, object)
+ {
+ return this.create(Gitana.BranchMap, repository, object);
+ },
+
+ changesetMap: function(repository, object)
+ {
+ return this.create(Gitana.ChangesetMap, repository, object);
+ },
+
+ releaseMap: function(repository, object)
+ {
+ return this.create(Gitana.ReleaseMap, repository, object);
+ },
+
+ mergeConflictMap: function(repository, object)
+ {
+ return this.create(Gitana.MergeConflictMap, repository, object);
+ },
+
+ nodeMap: function(branch, object)
+ {
+ return this.create(Gitana.NodeMap, branch, object);
+ },
+
+ deletionMap: function(branch, object)
+ {
+ return this.create(Gitana.DeletionMap, branch, object);
+ },
+
+ definition: function(branch, object)
+ {
+ return this.create(Gitana.Definition, branch, object);
+ },
+
+ form: function(branch, object)
+ {
+ return this.create(Gitana.Form, branch, object);
+ },
+
+ traversalResults: function(branch, object)
+ {
+ return this.create(Gitana.TraversalResults, branch, object);
+ },
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // DOMAINS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ domainPrincipal: function(domain, object)
+ {
+ // create the principal
+ const principal = this.create(Gitana.DomainPrincipal, domain, object);
+
+ // extend the principal pre-emptively if we have an object
+ if (object)
+ {
+ this.extendPrincipal(principal);
+ }
+
+ return principal;
+ },
+
+ domainPrincipalMap: function(domain, object)
+ {
+ return this.create(Gitana.PrincipalMap, domain, object);
+ },
+
+ extendPrincipal: function(principal)
+ {
+ if (principal.getType() && principal.objectType() === "Gitana.DomainPrincipal")
+ {
+ if (principal.getType() === "USER")
+ {
+ Gitana.stampInto(principal, Gitana.DomainUser);
+ }
+ else if (principal.getType() === "GROUP")
+ {
+ Gitana.stampInto(principal, Gitana.DomainGroup);
+ }
+ }
+ },
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // VAULTS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ archive: function(vault, object)
+ {
+ return this.create(Gitana.Archive, vault, object);
+ },
+
+ archiveMap: function(vault, object)
+ {
+ return this.create(Gitana.ArchiveMap, vault, object);
+ },
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // MISCELLANEOUS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ team: function(cluster, teamable, object)
+ {
+ return new Gitana.Team(cluster, teamable, object);
+ },
+
+ teamMap: function(cluster, teamable, object)
+ {
+ return new Gitana.TeamMap(cluster, teamable, object);
+ },
+
+ activity: function(datastore, object)
+ {
+ return new Gitana.Activity(datastore, object);
+ },
+
+ activityMap: function(datastore, object)
+ {
+ return new Gitana.ActivityMap(datastore, object);
+ },
+
+ role: function(cluster, roleContainer, object)
+ {
+ return new Gitana.Role(cluster, roleContainer, object);
+ },
+
+ roleMap: function(cluster, roleContainer, object)
+ {
+ return new Gitana.RoleMap(cluster, roleContainer, object);
+ },
+
+ resultMap: function(driver, resultMap)
+ {
+ return new Gitana.ResultMap(driver, resultMap);
+ },
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // REGISTRAR
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ tenant: function(registrar, object)
+ {
+ return this.create(Gitana.Tenant, registrar, object);
+ },
+
+ tenantMap: function(registrar, object)
+ {
+ return this.create(Gitana.TenantMap, registrar, object);
+ },
+
+ plan: function(registrar, object)
+ {
+ return this.create(Gitana.Plan, registrar, object);
+ },
+
+ planMap: function(registrar, object)
+ {
+ return this.create(Gitana.PlanMap, registrar, object);
+ },
+
+ meter: function(registrar, object)
+ {
+ return this.create(Gitana.Meter, registrar, object);
+ },
+
+ meterMap: function(registrar, object)
+ {
+ return this.create(Gitana.MeterMap, registrar, object);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // DIRECTORY
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ identity: function(directory, object)
+ {
+ return this.create(Gitana.Identity, directory, object);
+ },
+
+ identityMap: function(directory, object)
+ {
+ return this.create(Gitana.IdentityMap, directory, object);
+ },
+
+ connection: function(directory, object)
+ {
+ return this.create(Gitana.Connection, directory, object);
+ },
+
+ connectionMap: function(directory, object)
+ {
+ return this.create(Gitana.ConnectionMap, directory, object);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // APPLICATION
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ settings: function(application, object)
+ {
+ return this.create(Gitana.Settings, application, object);
+ },
+
+ settingsMap: function(application, object)
+ {
+ return this.create(Gitana.SettingsMap, application, object);
+ },
+
+ registration: function(application, object)
+ {
+ return this.create(Gitana.Registration, application, object);
+ },
+
+ registrationMap: function(application, object)
+ {
+ return this.create(Gitana.RegistrationMap, application, object);
+ },
+
+ pageRendition: function(application, object)
+ {
+ return this.create(Gitana.PageRendition, application, object);
+ },
+
+ pageRenditionMap: function(application, object)
+ {
+ return this.create(Gitana.PageRenditionMap, application, object);
+ },
+
+ email: function(application, object)
+ {
+ return this.create(Gitana.Email, application, object);
+ },
+
+ emailMap: function(application, object)
+ {
+ return this.create(Gitana.EmailMap, application, object);
+ },
+
+ emailProvider: function(application, object)
+ {
+ return this.create(Gitana.EmailProvider, application, object);
+ },
+
+ emailProviderMap: function(application, object)
+ {
+ return this.create(Gitana.EmailProviderMap, application, object);
+ },
+
+ message: function(application, object)
+ {
+ return this.create(Gitana.Message, application, object);
+ },
+
+ messageMap: function(application, object)
+ {
+ return this.create(Gitana.MessageMap, application, object);
+ }
+
+ });
+
+ // static methods for registration
+ Gitana.ObjectFactory.registry = { };
+ Gitana.ObjectFactory.register = function(qname, objectClass)
+ {
+ Gitana.ObjectFactory.registry[qname] = objectClass;
+ };
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.AbstractPersistable = Gitana.Chainable.extend(
+ /** @lends Gitana.AbstractPersistable.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.Chainable
+ *
+ * @class Abstract base class for abstract objects and maps
+ *
+ * @param {Gitana} driver Gitana driver
+ * @param {Object} [object]
+ */
+ constructor: function(driver, object)
+ {
+ this.base(driver);
+
+ // auto-load response
+ if (object)
+ {
+ this.handleResponse.call(this, object);
+ }
+ },
+
+ /**
+ * @EXTENSION_POINT
+ *
+ * Convert the json response object into the things we want to preserve on the object.
+ * This should set the "object" property but may choose to set other properties as well.
+ *
+ * @param response
+ */
+ handleResponse: function(response)
+ {
+ // remove our properties (not functions)
+ Gitana.deleteProperties(this, false);
+
+ // special handling - if response contains "_ref", remove it
+ if (response["_ref"]) {
+ delete response["_ref"];
+ }
+
+ // copy properties
+ Gitana.copyInto(this, response);
+
+ // handle any system properties
+ this.handleSystemProperties(response);
+ },
+
+ /**
+ * Gets called after the response is handled and allows the object to pull out special values from
+ * the "object" field so that they don't sit on the JSON object
+ */
+ handleSystemProperties: function(response)
+ {
+ // utilize the chainCopyState method in case the response is a Gitana object
+ this.chainCopyState(response);
+ },
+
+ /**
+ * Hands back a cleanup, properties-only JSON simple object.
+ */
+ json: function()
+ {
+ return JSON.parse(JSON.stringify(this));
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.AbstractMap = Gitana.AbstractPersistable.extend(
+ /** @lends Gitana.AbstractMap.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractPersistable
+ *
+ * @class Abstract base class for a map of Gitana objects
+ *
+ * @param {Gitana} driver
+ * @param {Object} object
+ */
+ constructor: function(driver, object)
+ {
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // PRIVILEGED METHODS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ // auto-manage a list of keys
+ this.__keys = (function() {
+ const list = [];
+ return function(x) {
+ if (!Gitana.isUndefined(x)) {
+ if (x === 'empty') {
+ while (list.length > 0) { list.shift(); }
+ } else {
+ if (!x && x.length) {
+ for (let i = 0; i < x.length; i++) {
+ list.push(x[i]);
+ }
+ }
+ else
+ {
+ list.push(x);
+ }
+ }
+ }
+ return list;
+ };
+ })();
+
+ this.__totalRows = (function() {
+ let _totalRows = null;
+ return function(totalRows) {
+ if (!Gitana.isUndefined(totalRows)) { _totalRows = totalRows; }
+ return _totalRows;
+ };
+ })();
+
+ this.__size = (function() {
+ let _size = null;
+ return function(size) {
+ if (!Gitana.isUndefined(size)) { _size = size; }
+ return _size;
+ };
+ })();
+
+ this.__offset = (function() {
+ let _offset = 0;
+ return function(offset) {
+ if (!Gitana.isUndefined(offset) && offset >= 0) { _offset = offset; }
+ return _offset;
+ };
+ })();
+
+ this.base(driver, object);
+
+ // in case the incoming object is a state-carrying object (like another map)
+ if (object)
+ {
+ this.chainCopyState(object);
+ }
+ },
+
+ refs: function()
+ {
+ const references = [];
+
+ for (let i = 0; i < this.__keys().length; i++)
+ {
+ const key = this.__keys()[i];
+
+ const object = this[key];
+ if (object.ref)
+ {
+ references.push(object.ref());
+ }
+ }
+
+ return references;
+ },
+
+ /**
+ * Override to include:
+ *
+ * __keys
+ * __totalRows
+ * __size
+ * __offset
+ *
+ * @param otherObject
+ */
+ chainCopyState: function(otherObject)
+ {
+ this.base(otherObject);
+
+ // include keys
+ if (otherObject.__keys) {
+ this.__keys('empty');
+ for (let i = 0; i < otherObject.__keys().length; i++)
+ {
+ const k = otherObject.__keys()[i];
+ this.__keys().push(k);
+ }
+ }
+
+ if (otherObject.__totalRows) {
+ this.__totalRows(otherObject.__totalRows());
+ }
+ if (otherObject.__size) {
+ this.__size(otherObject.__size());
+ }
+ if (otherObject.__offset) {
+ this.__offset(otherObject.__offset());
+ }
+
+ },
+
+ clear: function()
+ {
+ // clear object properties (but not member functions)
+ Gitana.deleteProperties(this, false);
+
+ // empty keys
+ this.__keys('empty');
+ },
+
+ /**
+ * @override
+ *
+ * Convert the json response object into the things we want to preserve on the object.
+ * This should set the "object" property but may choose to set other properties as well.
+ *
+ * @param response
+ */
+ handleResponse: function(response)
+ {
+ this.clear();
+
+ if (response)
+ {
+ // is it a gitana map?
+ if (response.totalRows && response.size && response.offset) {
+ this.__totalRows(response.totalRows());
+ this.__size(response.size());
+ this.__offset(response.offset());
+ }
+ else
+ {
+ // otherwise assume it is a gitana result set
+ this.__totalRows(response["total_rows"]);
+ this.__size(response["size"]);
+ this.__offset(response["offset"]);
+ }
+
+ if (response.rows)
+ {
+ // parse array
+ if (Gitana.isArray(response.rows))
+ {
+ for (let i = 0; i < response.rows.length; i++)
+ {
+ const o = this.buildObject(response.rows[i]);
+ this[o.getId()] = o;
+
+ this.__keys().push(o.getId());
+ }
+ }
+ else
+ {
+ // parse object
+ for (let key in response.rows)
+ {
+ if (response.rows.hasOwnProperty(key) && !Gitana.isFunction(response.rows[key]))
+ {
+ const value = response.rows[key];
+
+ const o = this.buildObject(value);
+
+ // determine key
+ let k = (o.getId && o.getId());
+ if (!k) {
+ k = key;
+ }
+ this[k] = o;
+
+ this.__keys().push(k);
+ }
+ }
+ }
+ this.__size(this.__keys().length);
+ }
+ else
+ {
+ // otherwise, assume it is key/value pairs
+ // it also might be another Gitana Map
+
+ for (let key in response)
+ {
+ if (response.hasOwnProperty(key) && !Gitana.isFunction(response[key]))
+ {
+ const value = response[key];
+
+ const o = this.buildObject(value);
+
+ // determine key
+ let k = (o.getId && o.getId());
+ if (!k) {
+ k = key;
+ }
+ this[k] = o;
+
+ this[k] = o;
+
+ this.__keys().push(k);
+ }
+ }
+ this.__size(this.__keys().length);
+ }
+ }
+ },
+
+ /**
+ * @abstract
+ *
+ * @param json
+ */
+ buildObject: function(json)
+ {
+
+ },
+
+ get: function(key)
+ {
+ return this[key];
+ },
+
+ asArray: function()
+ {
+ const array = [];
+ for (let i = 0; i < this.__keys().length; i++)
+ {
+ const k = this.__keys()[i];
+
+ array.push(this[k]);
+ }
+
+ return array;
+ },
+
+ size: function(callback)
+ {
+ if (callback)
+ {
+ return this.then(function() {
+ callback.call(this, this.__size());
+ });
+ }
+
+ return this.__size();
+ },
+
+ offset: function(callback)
+ {
+ if (callback)
+ {
+ return this.then(function() {
+ callback.call(this, this.__offset());
+ });
+ }
+
+ return this.__offset();
+ },
+
+ totalRows: function(callback)
+ {
+ if (callback)
+ {
+ return this.then(function() {
+ callback.call(this, this.__totalRows());
+ });
+ }
+
+ return this.__totalRows();
+ },
+
+
+ /**
+ * Iterates over the map and fires the callback function in SERIAL for each element in the map.
+ * The scope for the callback is the object from the map (i.e. repository object, node object).
+ *
+ * The arguments to the callback function are (key, value) where value is the same as "this".
+ *
+ * NOTE: This works against elements in the map in SERIAL. One at a time. If you are doing concurrent
+ * remote operations for members of the set such that each operation is independent, you may want to use
+ * the eachX() method.
+ *
+ * @chained this
+ *
+ * @param callback
+ */
+ each: function(callback)
+ {
+ return this.then(function() {
+
+ // run functions
+ for (let i = 0; i < this.__keys().length; i++)
+ {
+ // key and value from the map
+ const key = this.__keys()[i];
+ const value = this[key];
+
+ // a function that fires our callback
+ // wrap in a closure so that we store the callback and key
+ // note: this = the value wrapped in a chain, so we don't pass in value
+ const f = function(callback, key, index, map)
+ {
+ return function()
+ {
+ callback.call(this, key, this, index);
+
+ // manually copy resulting value back
+ Gitana.deleteProperties(map[key]);
+ Gitana.copyInto(map[key], this);
+ };
+
+ }(callback, key, i, this);
+
+ // create subchain mounted on this chainable and the run function
+ this.subchain(value).then(f);
+ }
+
+ return this;
+ });
+ },
+
+ /**
+ * Iterates over the map and fires the callback function in PARALLEL for each element in the map.
+ * The scope for the callback is the object from the map (i.e. repository object, node object).
+ *
+ * The arguments to the callback function are (key, value) where value is the same as "this".
+ *
+ * NOTE: This works against elements in the map in PARALLEL. All map members are fired against at the same
+ * time on separate timeouts. There is no guaranteed order for their completion. If you require serial
+ * execution, use the each() method.
+ *
+ * @chained
+ *
+ * @param callback
+ */
+ eachX: function(callback)
+ {
+ return this.then(function() {
+
+ // create an array of functions that invokes the callback for each element in the array
+ const functions = [];
+ for (let i = 0; i < this.__keys().length; i++)
+ {
+ const key = this.__keys()[i];
+ const value = this[key];
+
+ const f = function(callback, key, value, index) {
+
+ return function()
+ {
+ // NOTE: we're running a parallel chain that is managed for us by the Chain then() method.
+ // we can't change the parallel chain but we can subchain from it
+ // in our subchain we run our method
+ // the parallel chain kind of acts like one-hop noop so that we can take over and do our thing
+ this.subchain(value).then(function() {
+ callback.call(this, key, this, index);
+ });
+ };
+
+ }(callback, key, value, i);
+
+ functions.push(f);
+ }
+
+ // kick off all these functions in parallel
+ // adding them to the subchain
+ return this.then(functions);
+ });
+ },
+
+ /**
+ * Iterates over the map and applies the callback filter function to each element.
+ * It should hand back true if it wants to keep the value and false to remove it.
+ *
+ * NOTE: the "this" for the callback is the object from the map.
+ *
+ * @chained
+ *
+ * @param callback
+ */
+ filter: function(callback)
+ {
+ return this.then(function() {
+
+ const keysToKeep = [];
+ const keysToRemove = [];
+
+ for (let i = 0; i < this.__keys().length; i++)
+ {
+ const key = this.__keys()[i];
+ const object = this[key];
+
+ const keepIt = callback.call(object);
+ if (keepIt)
+ {
+ keysToKeep.push(key);
+ }
+ else
+ {
+ keysToRemove.push(key);
+ }
+ }
+
+ // remove any keys we don't want from the map
+ for (let i = 0; i < keysToRemove.length; i++)
+ {
+ delete this[keysToRemove[i]];
+ }
+
+ // swap keys to keep
+ // NOTE: we first clear the keys but we can't use slice(0,0) since that produces a NEW array
+ // instead, do this shift trick
+ this.__keys('empty');
+ for (let i = 0; i < keysToKeep.length; i++)
+ {
+ this.__keys().push(keysToKeep[i]);
+ }
+ });
+ },
+
+ /**
+ * Applies a comparator to sort the map.
+ *
+ * If no comparator is applied, the map will be sorted by its modification timestamp (if possible).
+ *
+ * The comparator can be a string that uses dot-notation to identify a field in the JSON that
+ * should be sorted. (example: "title" or "property1.property2.property3")
+ *
+ * Finally, the comparator can be a function. It takes (previousValue, currentValue) and hands back:
+ * -1 if the currentValue is less than the previousValue (should be sorted lower)
+ * 0 if they are equivalent
+ * 1 if they currentValue is greater than the previousValue (should be sorted higher)
+ *
+ * @chained
+ *
+ * @param comparator
+ */
+ sort: function(comparator)
+ {
+ return this.then(function() {
+
+ // build a temporary array of values
+ const array = [];
+ for (let i = 0; i < this.__keys().length; i++) {
+ const key = this.__keys()[i];
+ array.push(this[key]);
+ }
+
+ // sort the array
+ array.sort(comparator);
+
+ // now reset keys according to the order of the array
+ this.__keys("empty");
+ for (let i = 0; i < array.length; i++)
+ {
+ this.__keys().push(array[i].getId());
+ }
+
+ });
+ },
+
+ /**
+ * Limits the number of elements in the map.
+ *
+ * @chained
+ *
+ * @param size
+ */
+ limit: function(size)
+ {
+ return this.then(function() {
+
+ const keysToRemove = [];
+
+ if (size > this.__keys().length)
+ {
+ // keep everything
+ return;
+ }
+
+ // figure out which keys to remove
+ for (let i = 0; i < this.__keys().length; i++)
+ {
+ if (i >= size)
+ {
+ keysToRemove.push(this.__keys()[i]);
+ }
+ }
+
+ // truncate the keys
+ // NOTE: we can't use slice here since that produces a new array
+ while (this.__keys().length > size)
+ {
+ this.__keys().pop();
+ }
+
+ // remove any keys to remove from map
+ for (let i = 0; i < keysToRemove.length; i++)
+ {
+ delete this[keysToRemove[i]];
+ }
+
+ // reset the size
+ this.__size(this.__keys().length);
+ });
+ },
+
+ /**
+ * Paginates elements in the map.
+ *
+ * @chained
+ *
+ * @param pagination
+ */
+ paginate: function(pagination)
+ {
+ return this.then(function() {
+
+ const skip = pagination.skip;
+ const limit = pagination.limit;
+
+ const keysToRemove = [];
+
+ // figure out which keys to remove
+ for (let i = 0; i < this.__keys().length; i++)
+ {
+ if (i < skip || i >= skip + limit)
+ {
+ keysToRemove.push(this.__keys()[i]);
+ }
+ }
+
+ // truncate the keys
+ // NOTE: we can't use slice here since that produces a new array
+ while (this.__keys().length > limit + skip)
+ {
+ this.__keys().pop();
+ }
+
+ // remove any keys to remove from map
+ for (let i = 0; i < keysToRemove.length; i++)
+ {
+ delete this[keysToRemove[i]];
+ }
+
+ // reset the limit
+ this.__size(this.__keys().length);
+ });
+ },
+
+ /**
+ * Counts the number of elements in the map and fires it into a callback function.
+ */
+ count: function(callback)
+ {
+ if (callback)
+ {
+ return this.then(function() {
+ callback.call(this, this.__keys().length);
+ });
+ }
+
+ return this.__keys().length;
+ },
+
+ /**
+ * Keeps the first element in the map
+ */
+ keepOne: function(emptyHandler)
+ {
+ const self = this;
+
+ let json = {};
+ if (this.__keys().length > 0)
+ {
+ json = this[this.__keys()[0]];
+ }
+
+ const chainable = this.buildObject(json);
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ this.subchain(self).then(function() {
+
+ if (this.__keys().length > 0)
+ {
+ const obj = this[this.__keys()[0]];
+
+ if (chain.loadFrom)
+ {
+ // for objects, like nodes or branches
+ chain.loadFrom(obj);
+ }
+ else
+ {
+ // non-objects? (i.e. binary or attachment maps)
+ chain.handleResponse(obj);
+ }
+ }
+ else
+ {
+ const err = new Gitana.Error();
+ err.name = "Empty Map";
+ err.message = "The map doesn't have any elements in it";
+
+ if (emptyHandler)
+ {
+ emptyHandler.call(self, err);
+ }
+ else
+ {
+ this.error(err);
+ }
+
+ return false;
+ }
+
+ });
+ });
+ },
+
+ /**
+ * Selects an individual element from the map and continues the chain.
+ *
+ * @param key
+ */
+ select: function(key)
+ {
+ const self = this;
+
+ let json = {};
+ if (this[key])
+ {
+ json = this[key];
+ }
+
+ // what we hand back
+ const result = this.subchain(this.buildObject(json));
+
+ // preload some work
+ return result.then(function() {
+
+ const chain = this;
+
+ this.subchain(self).then(function() {
+
+ const obj = this[key];
+ if (!obj)
+ {
+ const err = new Gitana.Error();
+ err.name = "No element with key: " + key;
+ err.message = err.name;
+
+ this.error(err);
+
+ return false;
+ }
+
+ if (result.loadFrom)
+ {
+ // for objects, like nodes or branches
+ chain.loadFrom(obj);
+ }
+ else
+ {
+ // non-objects? (i.e. binary or attachment maps)
+ chain.handleResponse(obj);
+ }
+
+ });
+ });
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.AbstractObject = Gitana.AbstractPersistable.extend(
+ /** @lends Gitana.AbstractObject.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractPersistable
+ *
+ * @class Abstract base class for Gitana document objects.
+ *
+ * @param {Gitana} driver
+ * @param {Object} [object]
+ */
+ constructor: function(driver, object)
+ {
+ this.__system = (function() {
+ const _system = new Gitana.SystemMetadata();
+ return function(system) {
+ if (!Gitana.isUndefined(system)) { _system.updateFrom(system); }
+ return _system;
+ };
+ })();
+
+
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // INSTANCE CHAINABLE METHODS
+ //
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Executes an HTTP delete for this object and continues the chain with the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ */
+ this.chainDelete = function(chainable, uri, params)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ // delete
+ chain.getDriver().gitanaDelete(uri, params, function() {
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Reloads this object from the server and then passes control to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ */
+ this.chainReload = function(chainable, uri, params)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ // reload
+ chain.getDriver().gitanaGet(uri, params, {}, function(obj) {
+ chain.handleResponse(obj);
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Executes an update (write + read) of this object and then passes control to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ */
+ this.chainUpdate = function(chainable, uri, params)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ // delete
+ chain.getDriver().gitanaPut(uri, params, chain, function() {
+ chain.getDriver().gitanaGet(uri, params, {}, function(obj) {
+ chain.handleResponse(obj);
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+ /**
+ * Performs a PATCH to the server and populates the chainable with results.
+ * Proceeds with the chain as bound to the chainable.
+ *
+ * @param chainable
+ * @param uri
+ * @param params
+ * @param payload
+ */
+ this.chainPatch = function(chainable, uri, params, payload)
+ {
+ const self = this;
+
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // allow for closures on uri for late resolution
+ if (Gitana.isFunction(uri)) {
+ uri = uri.call(self);
+ }
+
+ // create
+ driver.gitanaPatch(uri, params, payload, function(response) {
+ chain.handleResponse(response);
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ };
+
+
+ // finally chain to parent prototype
+ this.base(driver, object);
+ },
+
+ /**
+ * Override to include:
+ *
+ * __system
+ *
+ * @param otherObject
+ */
+ chainCopyState: function(otherObject)
+ {
+ this.base(otherObject);
+
+ // include __system properties?
+ if (otherObject.__system) {
+ this.__system(otherObject.__system());
+ }
+ },
+
+ /**
+ * @EXTENSION_POINT
+ */
+ getUri: function()
+ {
+ return null;
+ },
+
+ /**
+ * @abstract
+ */
+ getType: function()
+ {
+ return null;
+ },
+
+ /**
+ * @abstract
+ *
+ * @returns {String} a string denoting a reference to this object.
+ */
+ ref: function()
+ {
+ return null;
+ },
+
+ /**
+ * Hands back the URI of this object as referenced by the browser.
+ */
+ getProxiedUri: function()
+ {
+ return this.getDriver().baseURL + this.getUri();
+ },
+
+ /**
+ * Get a json property
+ *
+ * @param key
+ */
+ get: function(key)
+ {
+ return this[key];
+ },
+
+ /**
+ * Set a json property
+ *
+ * @param key
+ * @param value
+ */
+ set: function(key ,value)
+ {
+ this[key] = value;
+ },
+
+ /**
+ * Hands back the ID ("_doc") of this object.
+ *
+ * @public
+ *
+ * @returns {String} id
+ */
+ getId: function()
+ {
+ return this.get("_doc");
+ },
+
+ /**
+ * Hands back the system metadata for this object.
+ *
+ * @public
+ *
+ * @returns {Gitana.SystemMetadata} system metadata
+ */
+ getSystemMetadata: function()
+ {
+ return this.__system();
+ },
+
+ /**
+ * The title for the object.
+ *
+ * @public
+ *
+ * @returns {String} the title
+ */
+ getTitle: function()
+ {
+ return this.get("title");
+ },
+
+ /**
+ * The description for the object.
+ *
+ * @public
+ *
+ * @returns {String} the description
+ */
+ getDescription: function()
+ {
+ return this.get("description");
+ },
+
+ // TODO: this is a temporary workaround at the moment
+ // it has to do all kinds of special treatment for _ variables because these variables are
+ // actually system managed but they're on the top level object.
+ //
+ // TODO:
+ // 1) gitana repo system managed properties should all be under _system
+ // 2) the system block should be pulled off the object on read and not required on write
+
+ /**
+ * Replaces all of the properties of this object with those of the given object.
+ * This method should be used to update the state of this object.
+ *
+ * Any functions from the incoming object will not be copied.
+ *
+ * @public
+ *
+ * @param object {Object} object containing the properties
+ */
+ replacePropertiesWith: function(object)
+ {
+ // create a copy of the incoming object
+ const candidate = {};
+ Gitana.copyInto(candidate, object);
+
+ // we don't allow the following values to be replaced
+ const backups = {};
+ backups["_doc"] = this["_doc"];
+ delete candidate["_doc"];
+ backups["_type"] = this["_type"];
+ delete candidate["_type"];
+ backups["_qname"] = this["_qname"];
+ delete candidate["_qname"];
+
+ // remove our properties (not functions)
+ Gitana.deleteProperties(this, false);
+
+ // restore
+ this["_doc"] = backups["_doc"];
+ this["_type"] = backups["_type"];
+ this["_qname"] = backups["_qname"];
+
+ // copy in candidate properties
+ Gitana.copyInto(this, candidate);
+ },
+
+ /**
+ * @override
+ */
+ handleSystemProperties: function(response)
+ {
+ this.base(response);
+
+ if (this["_system"])
+ {
+ // strip out system metadata
+ const json = this["_system"];
+ delete this["_system"];
+
+ // update system properties
+ this.__system().updateFrom(json);
+ }
+ },
+
+ /**
+ * Helper function to convert the object portion to JSON
+ *
+ * @param pretty
+ */
+ stringify: function(pretty)
+ {
+ return Gitana.stringify(this, pretty);
+ },
+
+ /**
+ * Helper method that loads this object from another object of the same type.
+ *
+ * For example, loading a node from another loaded node.
+ *
+ * @param anotherObject
+ */
+ loadFrom: function(anotherObject)
+ {
+ this.handleResponse(anotherObject);
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.AbstractSelfableObject = Gitana.AbstractObject.extend(
+ /** @lends Gitana.AbstractSelfableObject.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class Abstract base class for selfable Gitana document objects.
+ *
+ * @param {Gitana} driver
+ * @param {Object} [object]
+ */
+ constructor: function(driver, object)
+ {
+ // finally chain to parent prototype
+ this.base(driver, object);
+ },
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // SELFABLE
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Delete
+ *
+ * @chained this
+ *
+ * @public
+ */
+ del: function()
+ {
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri();
+ };
+
+ // NOTE: pass control back to the server instance
+ return this.chainDelete(this.getPlatform(), uriFunction);
+ },
+
+ /**
+ * Reload
+ *
+ * @chained this
+ *
+ * @public
+ */
+ reload: function()
+ {
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri();
+ };
+
+ return this.chainReload(null, uriFunction);
+ },
+
+ /**
+ * Update
+ *
+ * @chained this
+ *
+ * @public
+ */
+ update: function()
+ {
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri();
+ };
+
+ return this.chainUpdate(null, uriFunction);
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.AbstractSelfableACLObject = Gitana.AbstractSelfableObject.extend(
+ /** @lends Gitana.AbstractSelfableACLObject.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractSelfableObject
+ *
+ * @class Abstract base class for selfable ACL Gitana document objects.
+ *
+ * @param {Gitana} driver
+ * @param {Object} [object]
+ */
+ constructor: function(driver, object)
+ {
+ // finally chain to parent prototype
+ this.base(driver, object);
+ },
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ACL METHODS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Retrieve full ACL and pass into chaining method.
+ *
+ * @chained this
+ *
+ * @param callback
+ */
+ loadACL: function(callback)
+ {
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri() + "/acl/list";
+ };
+
+ return this.chainGetResponse(this, uriFunction).then(function(response) {
+ callback.call(this, response);
+ });
+ },
+
+ /**
+ * Retrieve list of authorities and pass into chaining method.
+ *
+ * @chained this
+ *
+ * @param {Gitana.DomainPrincipal|String} principal the principal or the principal id
+ * @param callback
+ */
+ listAuthorities: function(principal, callback)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri() + "/acl?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainGetResponseRows(this, uriFunction).then(function(response) {
+ callback.call(this, response);
+ });
+ },
+
+ /**
+ * Checks whether the given principal has a granted authority for this object.
+ * This passes the result (true/false) to the chaining function.
+ *
+ * @chained this
+ *
+ * @param {Gitana.DomainPrincipal|String} principal the principal or the principal id
+ * @param {String} authorityId the id of the authority
+ * @param callback
+ */
+ checkAuthority: function(principal, authorityId, callback)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri() + "/authorities/" + authorityId + "/check?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostResponse(this, uriFunction).then(function(response) {
+ callback.call(this, response["check"]);
+ });
+ },
+
+ /**
+ * Grants an authority to a principal against this object.
+ *
+ * @chained this
+ *
+ * @param {Gitana.DomainPrincipal|String} principal the principal or the principal id
+ * @param {String} authorityId the id of the authority
+ */
+ grantAuthority: function(principal, authorityId)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri() + "/authorities/" + authorityId + "/grant?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostEmpty(null, uriFunction);
+ },
+
+ /**
+ * Revokes an authority from a principal against this object.
+ *
+ * @chained this
+ *
+ * @param {Gitana.DomainPrincipal|String} principal the principal or the principal id
+ * @param {String} authorityId the id of the authority
+ */
+ revokeAuthority: function(principal, authorityId)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri() + "/authorities/" + authorityId + "/revoke?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostEmpty(null, uriFunction);
+ },
+
+ /**
+ * Revokes all authorities for a principal against the server.
+ *
+ * @chained this
+ *
+ * @param {Gitana.Principal|String} principal the principal or the principal id
+ */
+ revokeAllAuthorities: function(principal)
+ {
+ return this.revokeAuthority(principal, "all");
+ },
+
+ /**
+ * Loads the authority grants for a given set of principals.
+ *
+ * @chained this
+ *
+ * @param principalIds
+ * @param callback
+ */
+ loadAuthorityGrants: function(principalIds, callback)
+ {
+ if (!principalIds)
+ {
+ principalIds = [];
+ }
+
+ const json = {
+ "principals": principalIds
+ };
+
+ return this.chainPostResponse(this, this.getUri() + "/authorities", {}, json).then(function(response) {
+ callback.call(this, response);
+ });
+ },
+
+ /**
+ * Checks whether the given principal has a permission against this object.
+ * This passes the result (true/false) to the chaining function.
+ *
+ * @chained this
+ *
+ * @param {Gitana.DomainPrincipal|String} principal the principal or the principal id
+ * @param {String} permissionId the id of the permission
+ * @param callback
+ */
+ checkPermission: function(principal, permissionId, callback)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const uriFunction = function()
+ {
+ return self.getUri() + "/permissions/" + permissionId + "/check?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostResponse(this, uriFunction).then(function(response) {
+ callback.call(this, response["check"]);
+ });
+ }
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // END OF ACL METHODS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.DataStore = Gitana.AbstractObject.extend(
+ /** @lends Gitana.DataStore.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class DataStore
+ *
+ * @param {Gitana} driver
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(driver, object)
+ {
+ this.base(driver, object);
+ },
+
+ /**
+ * @abstract
+ */
+ getUri: function()
+ {
+ return null;
+ },
+
+ /**
+ * @abstract
+ */
+ getType: function()
+ {
+ return null;
+ },
+
+ /**
+ * @abstract
+ *
+ * @returns {String} a string denoting a reference to this datastore
+ */
+ ref: function()
+ {
+ return this.getType() + "://" + this.getId();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ACL METHODS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Retrieve full ACL and pass into chaining method.
+ *
+ * @chained this
+ *
+ * @param callback
+ */
+ loadACL: function(callback)
+ {
+ const uriFunction = function()
+ {
+ return this.getUri() + "/acl/list";
+ };
+
+ return this.chainGetResponse(this, uriFunction).then(function(response) {
+ callback.call(this, response);
+ });
+ },
+
+ /**
+ * Retrieve list of authorities and pass into chaining method.
+ *
+ * @chained this
+ *
+ * @param {Gitana.Principal|String} principal the principal or the principal id
+ * @param callback
+ */
+ listAuthorities: function(principal, callback)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/acl?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainGetResponseRows(this, uriFunction).then(function(response) {
+ callback.call(this, response);
+ });
+ },
+
+ /**
+ * Checks whether the given principal has a granted authority for this object.
+ * This passes the result (true/false) to the chaining function.
+ *
+ * @chained this
+ *
+ * @param {Gitana.Principal|String} principal the principal or the principal id
+ * @param {String} authorityId the id of the authority
+ * @param callback
+ */
+ checkAuthority: function(principal, authorityId, callback)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/authorities/" + authorityId + "/check?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostResponse(this, uriFunction).then(function(response) {
+ callback.call(this, response["check"]);
+ });
+ },
+
+ /**
+ * Grants an authority to a principal against this object.
+ *
+ * @chained this
+ *
+ * @param {Gitana.Principal|String} principal the principal or the principal id
+ * @param {String} authorityId the id of the authority
+ */
+ grantAuthority: function(principal, authorityId)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/authorities/" + authorityId + "/grant?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostEmpty(null, uriFunction);
+ },
+
+ /**
+ * Revokes an authority from a principal against this object.
+ *
+ * @chained this
+ *
+ * @param {Gitana.Principal|String} principal the principal or the principal id
+ * @param {String} authorityId the id of the authority
+ */
+ revokeAuthority: function(principal, authorityId)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/authorities/" + authorityId + "/revoke?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostEmpty(null, uriFunction);
+ },
+
+ /**
+ * Revokes all authorities for a principal against the server.
+ *
+ * @chained server
+ *
+ * @param {Gitana.Principal|String} principal the principal or the principal id
+ */
+ revokeAllAuthorities: function(principal)
+ {
+ return this.revokeAuthority(principal, "all");
+ },
+
+ /**
+ * Loads the authority grants for a given set of principals.
+ *
+ * @chained repository
+ *
+ * @param principalIds
+ * @param callback
+ */
+ loadAuthorityGrants: function(principalIds, callback)
+ {
+ if (!principalIds)
+ {
+ principalIds = [];
+ }
+
+ const json = {
+ "principals": principalIds
+ };
+
+ return this.chainPostResponse(this, this.getUri() + "/authorities", {}, json).then(function(response) {
+ callback.call(this, response);
+ });
+ },
+
+ /**
+ * Checks whether the given principal has a permission against this object.
+ * This passes the result (true/false) to the chaining function.
+ *
+ * @chained this
+ *
+ * @param {Gitana.Principal|String} principal the principal or the principal id
+ * @param {String} permissionId the id of the permission
+ * @param callback
+ */
+ checkPermission: function(principal, permissionId, callback)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/permissions/" + permissionId + "/check?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostResponse(this, uriFunction).then(function(response) {
+ callback.call(this, response["check"]);
+ });
+ },
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // END OF ACL METHODS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // TEAMABLE
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Reads a team.
+ *
+ * @param teamKey
+ *
+ * @chainable team
+ */
+ readTeam: function(teamKey)
+ {
+ const uriFunction = function()
+ {
+ return this.getUri() + "/teams/" + teamKey;
+ };
+
+ const chainable = this.getFactory().team(this.getPlatform(), this);
+ return this.chainGet(chainable, uriFunction);
+ },
+
+ /**
+ * Lists teams.
+ *
+ * @chainable map of teams
+ */
+ listTeams: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri() + "/teams";
+ };
+
+ const chainable = this.getFactory().teamMap(this.getCluster(), this);
+ return this.chainGet(chainable, uriFunction);
+ },
+
+ /**
+ * Creates a team.
+ *
+ * @param teamKey
+ * @param object
+ *
+ * @chainable team
+ */
+ createTeam: function(teamKey, object)
+ {
+ if (!object)
+ {
+ object = {};
+ }
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/teams?key=" + teamKey;
+ };
+
+ const self = this;
+
+ const chainable = this.getFactory().team(this.getPlatform(), this);
+ return this.chainPostResponse(chainable, uriFunction, {}, object).then(function() {
+
+ const chain = this;
+
+ Chain(self).readTeam(teamKey).then(function() {
+ chain.handleResponse(this);
+ chain.next();
+ });
+
+ // we manually advance the chain
+ return false;
+ });
+ },
+
+ /**
+ * Gets the owners team
+ *
+ * @chained team
+ */
+ readOwnersTeam: function()
+ {
+ return this.readTeam("owners");
+ },
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // END OF TEAMABLE
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ACTIVITIES
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Lists activities.
+ *
+ * @chained activity map
+ *
+ * @param {Object} pagination pagination (optional)
+ */
+ listActivities: function(pagination)
+ {
+ // prepare params (with pagination)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().activityMap(this);
+ return this.chainGet(chainable, "/activities", params);
+ },
+
+ /**
+ * Read an activity.
+ *
+ * @chained activity
+ *
+ * @param {String} activityId the activity id
+ */
+ readActivity: function(activityId)
+ {
+ const chainable = this.getFactory().activity(this);
+ return this.chainGet(chainable, "/activities/" + activityId);
+ },
+
+ /**
+ * Queries for activities.
+ *
+ * @chained activity map
+ *
+ * @param {Object} query query.
+ * @param {Object} pagination pagination (optional)
+ */
+ queryActivities: function(query, pagination)
+ {
+ const chainable = this.getFactory().activityMap(this);
+
+ // prepare params (with pagination)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ return this.chainPost(chainable, "/activities/query", params, query);
+ },
+
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ROLE CONTAINER
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Reads a role.
+ *
+ * @param roleKeyOrId
+ * @param inherited whether to check inherited role containers
+ *
+ * @chainable role
+ */
+ readRole: function(roleKeyOrId, inherited)
+ {
+ const params = {};
+
+ if (inherited)
+ {
+ params.inherited = true;
+ }
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/roles/" + roleKeyOrId;
+ };
+
+ const chainable = this.getFactory().role(this.getCluster(), this);
+ return this.chainGet(chainable, uriFunction, params);
+ },
+
+ /**
+ * Lists roles.
+ *
+ * @param inherited whether to draw from inherited role containers
+ *
+ * @chainable map of teams
+ */
+ listRoles: function(inherited)
+ {
+ const params = {};
+
+ if (inherited)
+ {
+ params.inherited = true;
+ }
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/roles";
+ };
+
+ const chainable = this.getFactory().roleMap(this.getCluster(), this);
+ return this.chainGet(chainable, uriFunction, params);
+ },
+
+ /**
+ * Creates a role.
+ *
+ * @param roleKey
+ * @param object
+ *
+ * @chainable team
+ */
+ createRole: function(roleKey, object)
+ {
+ if (!object)
+ {
+ object = {};
+ }
+ object.roleKey = roleKey;
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/roles";
+ };
+
+ const self = this;
+
+ const chainable = this.getFactory().role(this.getPlatform(), this, roleKey);
+ return this.chainPostResponse(chainable, uriFunction, {}, object).then(function() {
+ this.subchain(self).readRole(roleKey).then(function() {
+ Gitana.copyInto(chainable, this);
+ });
+ });
+ },
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // END OF ROLE CONTAINER
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // COMMON DATA STORE THINGS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ getMaxSize: function()
+ {
+ return this.get("maxSize");
+ },
+
+ getSize: function()
+ {
+ return this.get("size");
+ },
+
+ getObjectCount: function()
+ {
+ return this.get("objectcount");
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.ContainedDataStore = Gitana.DataStore.extend(
+ /** @lends Gitana.ContainedDataStore.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.DataStore
+ *
+ * @class ContainedDataStore
+ *
+ * @param {Gitana.DataStore} container
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(container, object)
+ {
+ this.base(container.getDriver(), object);
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // PRIVILEGED METHODS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.getContainer = function()
+ {
+ return container;
+ };
+
+ this.getContainerId = function()
+ {
+ return container.getId();
+ };
+
+ },
+
+ /**
+ * Delete
+ *
+ * @chained container datastore
+ *
+ * @public
+ */
+ del: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ // NOTE: pass control back to the container datastore instance
+ return this.chainDelete(this.getContainer(), uriFunction);
+ },
+
+ /**
+ * Reload
+ *
+ * @chained this
+ *
+ * @public
+ */
+ reload: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ return this.chainReload(null, uriFunction);
+ },
+
+ /**
+ * Update
+ *
+ * @chained this
+ *
+ * @public
+ */
+ update: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ return this.chainUpdate(null, uriFunction);
+ },
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // TRANSFER
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Exports an archive.
+ *
+ * @chained job
+ *
+ * @param {Object} settings
+ */
+ exportArchive: function(settings)
+ {
+ const self = this;
+
+ let vaultId = settings.vault;
+ if (!Gitana.isString(vaultId))
+ {
+ vaultId = vaultId.getId();
+ }
+ const groupId = settings.group;
+ const artifactId = settings.artifact;
+ const versionId = settings.version;
+ const configuration = (settings.configuration ? settings.configuration : {});
+ const synchronous = !settings.async;
+
+ // archive additional properties
+ const title = settings.title;
+ const description = settings.description;
+ const published = settings.published;
+
+ // we continue the chain with a job
+ const chainable = this.getFactory().job(this.getCluster(), "export");
+
+ // fire off import and job queue checking
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // create
+ const params = {};
+ params["vault"] = vaultId;
+ params["group"] = groupId;
+ params["artifact"] = artifactId;
+ params["version"] = versionId;
+ params["schedule"] = "ASYNCHRONOUS";
+ if (title) {
+ params["title"] = title;
+ }
+ if (description) {
+ params["description"] = description;
+ }
+ if (published) {
+ params["published"] = published;
+ }
+ this.getDriver().gitanaPost(self.getUri() + "/export", params, configuration, function(response) {
+ Gitana.handleJobCompletion(chain, self.getCluster(), response.getId(), synchronous);
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ },
+
+ /**
+ * Imports an archive.
+ *
+ * @chained job
+ *
+ * @param {Object} settings
+ * @param {Function} reportFn function
+ */
+ importArchive: function(settings, reportFn)
+ {
+ const self = this;
+
+ let vaultId = settings.vault;
+ if (!Gitana.isString(vaultId))
+ {
+ vaultId = vaultId.getId();
+ }
+ const groupId = settings.group;
+ const artifactId = settings.artifact;
+ const versionId = settings.version;
+ const configuration = (settings.configuration ? settings.configuration : {});
+ const synchronous = !settings.async;
+
+ // we continue the chain with a job
+ const chainable = this.getFactory().job(this.getCluster(), "import");
+
+ // fire off import and job queue checking
+ return this.subchain(chainable).then(function() {
+
+ const chain = this;
+
+ // create
+ this.getDriver().gitanaPost(self.getUri() + "/import?vault=" + vaultId + "&group=" + groupId + "&artifact=" + artifactId + "&version=" + versionId + "&schedule=ASYNCHRONOUS", {}, configuration, function(response) {
+
+ Gitana.handleJobCompletion(chain, self.getCluster(), response.getId(), synchronous, reportFn);
+
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.BinaryAttachment = Gitana.AbstractPersistable.extend(
+ /** @lends Gitana.BinaryAttachment.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractPersistable
+ *
+ * @class Binary Attachment
+ *
+ * @param {Object} persistable gitana object
+ * @param {Object} attachment
+ */
+ constructor: function(persistable, attachment)
+ {
+ this.base(persistable.getDriver(), attachment);
+
+ this.objectType = function() { return "Gitana.BinaryAttachment"; };
+
+ this.persistable = function() {
+ return persistable;
+ };
+ },
+
+ getId: function()
+ {
+ return this.attachmentId;
+ },
+
+ getLength: function()
+ {
+ return this.length;
+ },
+
+ getContentType: function()
+ {
+ return this.contentType;
+ },
+
+ getFilename: function()
+ {
+ return this.filename;
+ },
+
+ getUri: function()
+ {
+ return this.persistable().getUri() + "/attachments/" + this.getId();
+ },
+
+ getDownloadUri: function()
+ {
+ return this.getDriver().baseURL + this.getUri();
+ },
+
+ getPreviewUri: function(name, config)
+ {
+ if (!config)
+ {
+ config = {};
+ }
+
+ config.attachment = this.attachmentId;
+
+ return this.persistable().getPreviewUri(name, config);
+ },
+
+ /**
+ * Deletes the attachment, hands back control to the persistable.
+ *
+ * @chained persistable
+ */
+ del: function()
+ {
+ const self = this;
+
+ const result = this.subchain(this.persistable());
+
+ // our work (first in chain)
+ result.subchain(self).then(function() {
+
+ const chain = this;
+
+ // delete the attachment
+ this.getDriver().gitanaDelete(this.getUri(), null, function() {
+
+ chain.next();
+
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ return false;
+ });
+
+ return result;
+ },
+
+ /**
+ * Downloads the attachment.
+ *
+ * @chained attachment
+ * @param callback
+ */
+ download: function(callback)
+ {
+ const self = this;
+
+ return this.then(function() {
+
+ const chain = this;
+
+ // download
+ this.getDriver().gitanaDownload(this.getUri(), null, function(data) {
+ callback.call(self, data);
+ chain.next();
+ }, function(http) {
+ self.httpError(http);
+ });
+
+ return false;
+ });
+ },
+
+ stream: function(callback)
+ {
+ if (!Gitana.streamDownload) {
+ throw new Error("XHR streaming not supported - this method will only work when running in Node.js");
+ }
+
+ Gitana.streamDownload(this, function(err, stream) {
+ callback(err, stream);
+ });
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.BinaryAttachmentMap = Gitana.AbstractMap.extend(
+ /** @lends Gitana.BinaryAttachmentMap.prototype */
+ {
+ constructor: function(persistable, object)
+ {
+ this.objectType = function() { return "Gitana.BinaryAttachmentMap"; };
+
+ this.__persistable = (function() {
+ let _persistable = persistable;
+ return function(p) {
+ if (!Gitana.isUndefined(p)) { _persistable = p; }
+ return _persistable;
+ };
+ })();
+
+ if (!object)
+ {
+ object = this.__persistable().getSystemMetadata()["attachments"];
+ }
+
+ // must come at end because loading of object requires persistable() method
+ this.base(this.__persistable().getDriver(), object);
+ },
+
+ /**
+ * Override to include:
+ *
+ * __persistable
+ *
+ * @param otherObject
+ */
+ chainCopyState: function(otherObject)
+ {
+ this.base(otherObject);
+
+ if (otherObject.__persistable) {
+ this.__persistable(otherObject.__persistable());
+ }
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return new Gitana.BinaryAttachmentMap(this.__persistable(), this);
+ },
+
+ /**
+ * @param attachment
+ */
+ buildObject: function(attachment)
+ {
+ return new Gitana.BinaryAttachment(this.__persistable(), attachment);
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.Team = Gitana.AbstractObject.extend(
+ /** @lends Gitana.Team.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class Team
+ *
+ * @param {Gitana.Cluster} cluster
+ * @param {Object} teamable
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(cluster, teamable, object)
+ {
+ this.__teamable = (function() {
+ let _teamable = null;
+ return function(teamable) {
+ if (!Gitana.isUndefined(teamable)) { _teamable = teamable; }
+ return _teamable;
+ };
+ })();
+
+ this.__teamable(teamable);
+
+ this.objectType = function() { return "Gitana.Team"; };
+
+ this.getCluster = function()
+ {
+ return cluster;
+ };
+
+ this.base(cluster.getDriver(), object);
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return this.getFactory().team(this.getCluster(), this.__teamable(), this);
+ },
+
+ getUri: function()
+ {
+ return this.__teamable().getUri() + "/teams/" + this.getKey();
+ },
+
+ getType: function()
+ {
+ return "team";
+ },
+
+ /**
+ * Delete
+ *
+ * @chained team
+ *
+ * @public
+ */
+ del: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ // NOTE: pass control back to the teamable
+ return this.chainDelete(this.__teamable(), uriFunction);
+ },
+
+ /**
+ * Reload
+ *
+ * @chained team
+ *
+ * @public
+ */
+ reload: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ return this.chainReload(null, uriFunction);
+ },
+
+ /**
+ * Update
+ *
+ * @chained team
+ *
+ * @public
+ */
+ update: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ return this.chainUpdate(null, uriFunction);
+ },
+
+ /**
+ * Adds a member to the team.
+ *
+ * @param {String|Object} principal either the principal object or the principal id
+ *
+ * @chained team
+ */
+ addMember: function(principal)
+ {
+ const self = this;
+
+ const uriFunction = function()
+ {
+ const principalDomainQualifiedId = self.extractPrincipalDomainQualifiedId(principal);
+
+ return this.getUri() + "/members/add?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostEmpty(null, uriFunction);
+ },
+
+ /**
+ * Removes a member from the team.
+ *
+ * @param {String|Object} principal - either the principal object or the principal id
+ *
+ * @chained team
+ */
+ removeMember: function(principal)
+ {
+ const principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/members/remove?id=" + principalDomainQualifiedId;
+ };
+
+ return this.chainPostEmpty(null, uriFunction);
+ },
+
+ /**
+ * Checks whether a principal is a member of the team.
+ *
+ * @param {String|Object} principal -either the principal object or the principal id
+ * @param callback function(check)
+ *
+ * @chained team
+ */
+ hasMember: function(principal, callback)
+ {
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/members/check";
+ };
+
+ const params = {};
+ params.id = self.extractPrincipalDomainQualifiedId(principal);
+
+ return this.chainPostResponse(null, uriFunction, params).then(function(response) {
+ callback(response.belongs);
+ });
+ },
+
+
+ /**
+ * Lists members of a team
+ *
+ * @param pagination
+ *
+ * @chained principal map
+ */
+ listMembers: function(pagination)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return this.getUri() + "/members";
+ };
+
+ const chainable = new Gitana.TeamMemberMap(this);
+ return this.chainGet(chainable, uriFunction, params);
+ },
+
+ /**
+ * Grants an authority to this team.
+ *
+ * @param authorityId
+ *
+ * @chained team
+ */
+ grant: function(authorityId)
+ {
+ const uriFunction = function()
+ {
+ return this.getUri() + "/authorities/" + authorityId + "/grant";
+ };
+
+ return this.chainPostEmpty(null, uriFunction);
+ },
+
+ /**
+ * Revokes an authority from this team.
+ *
+ * @param authorityId
+ *
+ * @chained team
+ */
+ revoke: function(authorityId)
+ {
+ const uriFunction = function()
+ {
+ return this.getUri() + "/authorities/" + authorityId + "/revoke";
+ };
+
+ return this.chainPostEmpty(null, uriFunction);
+ },
+
+ /**
+ * Loads the authorities for this team and fires them into a callback.
+ *
+ * @param callback
+ *
+ * @chained team
+ */
+ loadAuthorities: function(callback)
+ {
+ const uriFunction = function()
+ {
+ return this.getUri() + "/authorities";
+ };
+
+ return this.chainGetResponse(this, uriFunction).then(function(response) {
+ callback.call(this, response["authorities"]);
+ });
+ },
+
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ACCESSORS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Returns the team key
+ */
+ getKey: function()
+ {
+ return this.get("key");
+ },
+
+ getGroupId: function()
+ {
+ return this.get("groupId");
+ },
+
+ getRoleKeys: function()
+ {
+ return this.get("roleKeys");
+ }
+
+
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.TeamMap = Gitana.AbstractMap.extend(
+ /** @lends Gitana.TeamMap.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractMap
+ *
+ * @class Map of teams
+ *
+ * @param {Gitana.Cluster} cluster Gitana cluster instance.
+ * @param {Object} teamable
+ * @param {Object} object
+ */
+ constructor: function(cluster, teamable, object)
+ {
+ this.__teamable = (function() {
+ let _teamable = null;
+ return function(teamable) {
+ if (!Gitana.isUndefined(teamable)) { _teamable = teamable; }
+ return _teamable;
+ };
+ })();
+
+ this.__teamable(teamable);
+
+ this.objectType = function() { return "Gitana.TeamMap"; };
+
+ this.getCluster = function()
+ {
+ return cluster;
+ };
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CALL THROUGH TO BASE CLASS (at the end)
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.base(cluster.getDriver(), object);
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return this.getFactory().teamMap(this.getCluster(), this.__teamable(), this);
+ },
+
+ /**
+ * @param json
+ */
+ buildObject: function(json)
+ {
+ return this.getFactory().team(this.getCluster(), this.__teamable(), json);
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.TeamMember = Gitana.AbstractObject.extend(
+ /** @lends Gitana.TeamMember.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class TeamMember
+ *
+ * @param {Gitana.Cluster} team
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(team, object)
+ {
+ this.base(team.getDriver(), object);
+
+ this.objectType = function() { return "Gitana.TeamMember"; };
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // PRIVILEGED METHODS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.getTeam = function() { return team; };
+ this.getCluster = function() { return team.getCluster(); };
+ this.getClusterId = function() { return team.getClusterId(); };
+ }
+
+ /*,
+
+ domain: function()
+ {
+ const self = this;
+
+ const result = this.subchain(new Gitana.Domain({
+ "_doc": this.domainId
+ }));
+
+ return result.then(function() {
+ // TODO: read the domain and populate
+ });
+ },
+
+ principal: function()
+ {
+ const self = this;
+
+ // domain
+ const domain = new Gitana.Domain({
+ ""
+ })
+ // temp web host
+ const webhost = new Gitana.WebHost(this.getPlatform());
+
+ // we hand back a deployed application and preload some work
+ const chainable = this.getFactory().deployedApplication(webhost);
+ return this.chainPost(chainable, uriFunction).then(function() {
+
+ // load the real web host
+ const webhostId = self["deployments"][deploymentKey]["webhost"];
+ this.subchain(this.getPlatform()).readWebHost(webhostId).then(function() {
+ webhost.loadFrom(this);
+ });
+
+ });
+ }
+ */
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.TeamMemberMap = Gitana.AbstractMap.extend(
+ /** @lends Gitana.TeamMemberMap.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractMap
+ *
+ * @class Map of team members
+ *
+ * @param {Gitana.Team} team
+ * @param {Object} object
+ */
+ constructor: function(team, object)
+ {
+ this.objectType = function() { return "Gitana.TeamMemberMap"; };
+
+ this.getTeam = function()
+ {
+ return team;
+ };
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CALL THROUGH TO BASE CLASS (at the end)
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.base(team.getDriver(), object);
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return new Gitana.TeamMemberMap(this.getTeam(), this);
+ },
+
+ /**
+ * @param json
+ */
+ buildObject: function(json)
+ {
+ return new Gitana.TeamMember(this.getTeam(), json);
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.Activity = Gitana.AbstractObject.extend(
+ /** @lends Gitana.Activity.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class Activity
+ *
+ * @param {Gitana.DataStore} datastore
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(datastore, object)
+ {
+ this.base(datastore.getDriver(), object);
+
+ this.objectType = function() { return "Gitana.Activity"; };
+
+ this.getDataStore = function()
+ {
+ return datastore;
+ };
+ },
+
+ getUri: function()
+ {
+ return this.getDataStore().getUri() + "/activities/" + this.getId();
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return new Gitana.Activity(this.getDataStore(), this);
+ },
+
+ /**
+ * Delete
+ *
+ * @chained datastore
+ *
+ * @public
+ */
+ del: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ // NOTE: pass control back to the datastore
+ return this.chainDelete(this.getDataStore(), uriFunction);
+ },
+
+ /**
+ * Reload
+ *
+ * @chained security group
+ *
+ * @public
+ */
+ reload: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ return this.chainReload(null, uriFunction);
+ },
+
+ /**
+ * Update
+ *
+ * @chained security group
+ *
+ * @public
+ */
+ update: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ return this.chainUpdate(null, uriFunction);
+ },
+
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ACCESSORS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ getType: function()
+ {
+ return this.get("type");
+ },
+
+ getTimestamp: function()
+ {
+ return this.get("timestamp");
+ },
+
+
+ // user
+
+ getUserDomainId: function()
+ {
+ return this.get("userDomainId");
+ },
+
+ getUserId: function()
+ {
+ return this.get("userId");
+ },
+
+ getUserTitle: function()
+ {
+ return this.get("userTitle");
+ },
+
+ getUserEmail: function()
+ {
+ return this.get("userEmail");
+ },
+
+ getUserName: function()
+ {
+ return this.get("userName");
+ },
+
+
+ // object
+
+ getObjectDataStoreTypeId: function()
+ {
+ return this.get("objectDatastoreTypeId");
+ },
+
+ getObjectDataStoreId: function()
+ {
+ return this.get("objectDatastoreId");
+ },
+
+ getObjectDataStoreTitle: function()
+ {
+ return this.get("objectDatastoreTitle");
+ },
+
+ getObjectTypeId: function()
+ {
+ return this.get("objectTypeId");
+ },
+
+ getObjectId: function()
+ {
+ return this.get("objectId");
+ },
+
+ getObjectTitle: function()
+ {
+ return this.get("objectTitle");
+ },
+
+
+ // other
+
+ getOtherDataStoreTypeId: function()
+ {
+ return this.get("otherDatastoreTypeId");
+ },
+
+ getOtherDataStoreId: function()
+ {
+ return this.get("otherDatastoreId");
+ },
+
+ getOtherDataStoreTitle: function()
+ {
+ return this.get("otherDatastoreTitle");
+ },
+
+ getOtherTypeId: function()
+ {
+ return this.get("otherTypeId");
+ },
+
+ getOtherId: function()
+ {
+ return this.get("otherId");
+ },
+
+ getOtherTitle: function()
+ {
+ return this.get("otherTitle");
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.ActivityMap = Gitana.AbstractMap.extend(
+ /** @lends Gitana.ActivityMap.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractMap
+ *
+ * @class Map of activities
+ *
+ * @param {Object} datastore Gitana datastore
+ * @param {Object} object
+ */
+ constructor: function(datastore, object)
+ {
+ this.objectType = function() { return "Gitana.ActivityMap"; };
+
+ this.getDataStore = function()
+ {
+ return datastore;
+ };
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CALL THROUGH TO BASE CLASS (at the end)
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.base(datastore.getDriver(), object);
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return this.getFactory().activityMap(this.getDataStore(), this);
+ },
+
+ /**
+ * @param json
+ */
+ buildObject: function(json)
+ {
+ return this.getFactory().activity(this.getDataStore(), json);
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.Role = Gitana.AbstractObject.extend(
+ /** @lends Gitana.Role.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class Role
+ *
+ * @param {Gitana.Cluster} cluster
+ * @param {Object} roleContainer
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(cluster, roleContainer, object)
+ {
+ this.base(cluster.getDriver(), object);
+
+ this.objectType = function() { return "Gitana.Role"; };
+
+ this.roleContainer = roleContainer;
+
+ this.getCluster = function()
+ {
+ return cluster;
+ };
+ },
+
+ getUri: function()
+ {
+ return this.roleContainer.getUri() + "/roles/" + this.getId();
+ },
+
+ getType: function()
+ {
+ return "role";
+ },
+
+ /**
+ * Delete
+ *
+ * @chained team
+ *
+ * @public
+ */
+ del: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ // NOTE: pass control back to the role container
+ return this.chainDelete(this.roleContainer, uriFunction);
+ },
+
+ /**
+ * Reload
+ *
+ * @chained role
+ *
+ * @public
+ */
+ reload: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ return this.chainReload(null, uriFunction);
+ },
+
+ /**
+ * Update
+ *
+ * @chained team
+ *
+ * @public
+ */
+ update: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri();
+ };
+
+ return this.chainUpdate(null, uriFunction);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ACCESSORS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Returns the role key
+ */
+ getRoleKey: function()
+ {
+ return this.roleKey;
+ },
+
+ getPermissions: function()
+ {
+ return this.object["permissions"];
+ }
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.RoleMap = Gitana.AbstractMap.extend(
+ /** @lends Gitana.RoleMap.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractMap
+ *
+ * @class Map of roles
+ *
+ * @param {Gitana.Cluster} cluster Gitana cluster instance.
+ * @param {Object} roleContainer container
+ * @param {Object} object
+ */
+ constructor: function(cluster, roleContainer, object)
+ {
+ this.objectType = function() { return "Gitana.RoleMap"; };
+
+ this.getCluster = function()
+ {
+ return cluster;
+ };
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CALL THROUGH TO BASE CLASS (at the end)
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.base(cluster.getDriver(), object);
+
+ this.roleContainer = roleContainer;
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return this.getFactory().roleMap(this.getCluster(), this.roleContainer, this);
+ },
+
+ /**
+ * @param json
+ */
+ buildObject: function(json)
+ {
+ return this.getFactory().role(this.getCluster(), this.roleContainer, json);
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.ResultMap = Gitana.AbstractMap.extend(
+ /** @lends Gitana.ResultMap.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractMap
+ *
+ * @class Generalized result maps
+ *
+ * @param {Gitana} driver
+ * @param {Object} resultMap
+ */
+ constructor: function(driver, resultMap)
+ {
+ this.objectType = function() { return "Gitana.ResultMap"; };
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CALL THROUGH TO BASE CLASS (at the end)
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.base(driver, resultMap);
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return this.getFactory().resultMap(this.getDriver(), this);
+ },
+
+ /**
+ * @param object
+ */
+ buildObject: function(object)
+ {
+ return object;
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.Cluster = Gitana.DataStore.extend(
+ /** @lends Gitana.Cluster.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.DataStore
+ *
+ * @class Cluster
+ *
+ * @param {Gitana.Driver} driver
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(driver, object)
+ {
+ this.objectType = function() { return "Gitana.Cluster"; };
+
+ this.base(driver, object);
+ },
+
+ /**
+ * @OVERRIDE
+ */
+ getUri: function()
+ {
+ return "";
+ },
+
+ /**
+ * @OVERRIDE
+ */
+ getType: function()
+ {
+ return Gitana.TypedIDConstants.TYPE_CLUSTER;
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return new Gitana.Cluster(this.getDriver(), this);
+ },
+
+ /**
+ * Loads the contained types for a type as a string array and passes it into a callback function.
+ *
+ * @param type
+ * @param callback
+ * @return this
+ */
+ loadContainedTypes: function(type, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/tools/types/contained/" + type;
+ };
+
+ return this.chainPostResponse(this, uriFunction).then(function(response) {
+ callback.call(this, response["types"]);
+ });
+ },
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // JOB METHODS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Queries for jobs.
+ *
+ * @chained job map
+ *
+ * @param {Object} query Query for finding a job.
+ * @param {Object} pagination pagination (optional)
+ */
+ queryJobs: function(query, pagination)
+ {
+ const chainable = this.getFactory().jobMap(this);
+
+ // prepare params (with pagination)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ return this.chainPost(chainable, "/jobs/query", params, query);
+ },
+
+ /**
+ * Read a job.
+ *
+ * @chained job
+ *
+ * @param {String} jobId
+ */
+ readJob: function(jobId)
+ {
+ const chainable = this.getFactory().job(this);
+
+ return this.chainGet(chainable, "/jobs/" + jobId);
+ },
+
+ /**
+ * Kills a job
+ *
+ * @chained server
+ *
+ * @param {String} jobId
+ */
+ killJob: function(jobId)
+ {
+ return this.chainPostEmpty(null, "/jobs/" + jobId + "/kill");
+ },
+
+ /**
+ * Queries for unstarted jobs.
+ *
+ * @chained job map
+ *
+ * @param {Object} query Query for finding a job.
+ * @param {Object} pagination pagination (optional)
+ */
+ queryUnstartedJobs: function(query, pagination)
+ {
+ const chainable = this.getFactory().jobMap(this);
+
+ // prepare params (with pagination)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ return this.chainPost(chainable, "/jobs/unstarted/query", params, query);
+ },
+
+ /**
+ * Queries for running jobs.
+ *
+ * @chained job map
+ *
+ * @param {Object} query Query for finding a job.
+ * @param {Object} pagination pagination (optional)
+ */
+ queryRunningJobs: function(query, pagination)
+ {
+ const chainable = this.getFactory().jobMap(this);
+
+ // prepare params (with pagination)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ return this.chainPost(chainable, "/jobs/running/query", params, query);
+ },
+
+ /**
+ * Queries for failed jobs.
+ *
+ * @chained job map
+ *
+ * @param {Object} query Query for finding a job.
+ * @param {Object} pagination pagination (optional)
+ */
+ queryFailedJobs: function(query, pagination)
+ {
+ const chainable = this.getFactory().jobMap(this);
+
+ // prepare params (with pagination)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ return this.chainPost(chainable, "/jobs/failed/query", params, query);
+ },
+
+ /**
+ * Queries for waiting jobs.
+ *
+ * @chained job map
+ *
+ * @param {Object} query Query for finding a job.
+ * @param {Object} pagination pagination (optional)
+ */
+ queryWaitingJobs: function(query, pagination)
+ {
+ const chainable = this.getFactory().jobMap(this);
+
+ // prepare params (with pagination)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ return this.chainPost(chainable, "/jobs/waiting/query", params, query);
+ },
+
+ /**
+ * Queries for finished jobs.
+ *
+ * @chained job map
+ *
+ * @param {Object} query Query for finding a job.
+ * @param {Object} pagination pagination (optional)
+ */
+ queryFinishedJobs: function(query, pagination)
+ {
+ const chainable = this.getFactory().jobMap(this);
+
+ // prepare params (with pagination)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ return this.chainPost(chainable, "/jobs/finished/query", params, query);
+ },
+
+ waitForJobCompletion: function(jobId, callback, progressCallback)
+ {
+ const chainable = this;
+
+ const f = function()
+ {
+ window.setTimeout(function() {
+
+ Chain(chainable).readJob(jobId).then(function() {
+
+ if(progressCallback && Gitana.isFunction(progressCallback)) {
+ progressCallback(this);
+ }
+
+ if (this.state === "FINISHED") {
+ callback(this);
+ chainable.next();
+ } else if (this.state === "ERROR") {
+ callback(this);
+ chainable.next();
+ } else {
+ f();
+ }
+ });
+
+ }, 1000);
+ };
+ f();
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.AbstractClusterObject = Gitana.AbstractObject.extend(
+ /** @lends Gitana.AbstractClusterObject.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class AbstractClusterObject
+ *
+ * @param {Gitana.Cluster} cluster
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(cluster, object)
+ {
+ this.base(cluster.getDriver(), object);
+
+ this.objectType = function() { return "Gitana.Job"; };
+
+ this.getCluster = function()
+ {
+ return cluster;
+ };
+ },
+
+ /**
+ * @OVERRIDE
+ */
+ ref: function()
+ {
+ return this.getType() + "://" + this.getId();
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.Job = Gitana.AbstractClusterObject.extend(
+ /** @lends Gitana.Job.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractClusterObject
+ *
+ * @class Job
+ *
+ * @param {Gitana.Cluster} cluster
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(cluster, object)
+ {
+ this.base(cluster, object);
+
+ this.objectType = function() { return "Gitana.Job"; };
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return new Gitana.Job(this.getCluster(), this);
+ },
+
+ /**
+ * @returns {String} the type id of the job
+ */
+ getType: function()
+ {
+ return this.get("type");
+ },
+
+ /**
+ * @returns {String} the id of the principal that this job will run as
+ */
+ getRunAsPrincipalId: function()
+ {
+ return this.get("runAsPrincipal");
+ },
+
+ /**
+ * @returns {String} the domain of the principal that this job will run as
+ */
+ getRunAsPrincipalDomainId: function()
+ {
+ return this.get("runAsPrincipalDomain");
+ },
+
+ /**
+ * @returns {String} the state of the job
+ */
+ getState: function()
+ {
+ return this.get("state");
+ },
+
+ /**
+ * @returns {String} the platform id
+ */
+ getPlatformId: function()
+ {
+ return this.get("platformId");
+ },
+
+ /**
+ * @returns {Number} the priority of the job
+ */
+ getPriority: function()
+ {
+ return this.get("priority");
+ },
+
+ /**
+ * @returns {Number} the number of attempts made to run this job
+ */
+ getAttempts: function()
+ {
+ return this.get("attempts");
+ },
+
+ /**
+ * @returns {Object} when the job is scheduled to start (or null)
+ */
+ getScheduledStartTime: function()
+ {
+ return this.get("schedule_start_ms");
+ },
+
+ /**
+ * @returns [Array] array of status log objects
+ */
+ getLogEntries: function()
+ {
+ return this.get("log_entries");
+ },
+
+ getCurrentThread: function()
+ {
+ return this.get("current_thread");
+ },
+
+ getCurrentServer: function()
+ {
+ return this.get("current_server");
+ },
+
+ getCurrentServerTimeStamp: function()
+ {
+ return this.get("current_server_timestamp");
+ },
+
+ getSubmittedBy: function()
+ {
+ return this.get("submitted_by");
+ },
+
+ getSubmittedTimestamp: function()
+ {
+ return this.get("submitted_timestamp");
+ },
+
+ getStarted: function()
+ {
+ return this.get("started");
+ },
+
+ getStartedBy: function()
+ {
+ return this.get("started_by");
+ },
+
+ getStartedTimestamp: function()
+ {
+ return this.get("started_timestamp");
+ },
+
+ getStopped: function()
+ {
+ return this.get("stopped");
+ },
+
+ getStoppedTimestamp: function()
+ {
+ return this.get("stopped_timestamp");
+ },
+
+ getPaused: function()
+ {
+ return this.get("paused");
+ },
+
+ getPausedBy: function()
+ {
+ return this.get("paused_by");
+ },
+
+ getPausedTimestamp: function()
+ {
+ return this.get("paused_timestamp");
+ }
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.JobMap = Gitana.AbstractMap.extend(
+ /** @lends Gitana.JobMap.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractMap
+ *
+ * @class Map of jobs
+ *
+ * @param {Gitana.Cluster} cluster Gitana cluster instance.
+ * @param {Object} object
+ */
+ constructor: function(cluster, object)
+ {
+ this.objectType = function() { return "Gitana.JobMap"; };
+
+ this.getCluster = function()
+ {
+ return cluster;
+ };
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CALL THROUGH TO BASE CLASS (at the end)
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.base(cluster.getDriver(), object);
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return this.getFactory().jobMap(this.getCluster(), this);
+ },
+
+ /**
+ * @param json
+ */
+ buildObject: function(json)
+ {
+ return this.getFactory().job(this.getCluster(), json);
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.LogEntry = Gitana.AbstractObject.extend(
+ /** @lends Gitana.LogEntry.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class LogEntry
+ *
+ * @param {Gitana.Platform} platform
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(platform, object)
+ {
+ this.base(platform.getDriver(), object);
+
+ this.objectType = function() { return "Gitana.LogEntry"; };
+
+ this.getPlatform = function()
+ {
+ return platform;
+ };
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return new Gitana.LogEntry(this.getPlatform(), this);
+ },
+
+ /**
+ * @OVERRIDE
+ */
+ getType: function()
+ {
+ return Gitana.TypedIDConstants.TYPE_LOG_ENTRY;
+ },
+
+ /**
+ * @returns {String} the id of the principal that logged this entry
+ */
+ getPrincipalId: function()
+ {
+ return this.get("principalId");
+ },
+
+ /**
+ * @returns {String} the id of the repository against which this log entry was logged (or null)
+ */
+ getRepositoryId: function()
+ {
+ return this.get("repositoryId");
+ },
+
+ /**
+ * @returns {String} the id of the branch against which this log entry was logged (or null)
+ */
+ getBranchId: function()
+ {
+ return this.get("branchId");
+ },
+
+ /**
+ * @returns {String} log level
+ */
+ getLevel: function()
+ {
+ return this.get("level");
+ },
+
+ /**
+ * @returns {String} thread id
+ */
+ getThread: function()
+ {
+ return this.get("thread");
+ },
+
+ /**
+ * @returns {Object} timestamp
+ */
+ getTimestamp: function()
+ {
+ return this.get("timestamp");
+ },
+
+ /**
+ * @returns {String} message
+ */
+ getMessage: function()
+ {
+ return this.get("message");
+ },
+
+ /**
+ * @returns {String} filename
+ */
+ getFilename: function()
+ {
+ return this.get("filename");
+ },
+
+ /**
+ * @returns {String} method
+ */
+ getMethod: function()
+ {
+ return this.get("method");
+ },
+
+ /**
+ * @returns {Number} line number
+ */
+ getLineNumber: function()
+ {
+ return this.get("line");
+ },
+
+ /**
+ * @returns {Object} class descriptor
+ */
+ getClassDescriptor: function()
+ {
+ return this.get("class");
+ },
+
+ /**
+ * @returns [Array] throwables
+ */
+ getThrowables: function()
+ {
+ return this.get("throwables");
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.LogEntryMap = Gitana.AbstractMap.extend(
+ /** @lends Gitana.LogEntryMap.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractMap
+ *
+ * @class Map of log entries
+ *
+ * @param {Gitana.Platform} platform Gitana server instance.
+ * @param {Object} object
+ */
+ constructor: function(platform, object)
+ {
+ this.objectType = function() { return "Gitana.LogEntryMap"; };
+
+ this.getPlatform = function()
+ {
+ return platform;
+ };
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CALL THROUGH TO BASE CLASS (at the end)
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ this.base(platform.getDriver(), object);
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return this.getFactory().logEntryMap(this.getPlatform(), this);
+ },
+
+ /**
+ * @param json
+ */
+ buildObject: function(json)
+ {
+ return this.getFactory().logEntry(this.getPlatform(), json);
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.CopyJob = Gitana.Job.extend(
+ /** @lends Gitana.CopyJob.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class CopyJob
+ *
+ * @param {Gitana.Cluster} cluster
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(cluster, object)
+ {
+ this.base(cluster, object);
+
+ this.objectType = function() { return "Gitana.CopyJob"; };
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return new Gitana.CopyJob(this.getCluster(), this);
+ },
+
+ getImports: function()
+ {
+ const importObjects = [];
+
+ const array = this.get("imports");
+ for (let i = 0; i < array.length; i++)
+ {
+ const object = array[i];
+
+ const sources = object["sources"];
+ const targets = object["targets"];
+
+ const importObject = {
+ sources,
+ targets,
+ getType: function()
+ {
+ return this.targets[this.targets.length - 1]["typeId"];
+ },
+ getSourceId: function()
+ {
+ return this.sources[this.sources.length - 1]["id"];
+ },
+ getTargetId: function()
+ {
+ return this.targets[this.targets.length - 1]["id"];
+ }
+ };
+ importObjects.push(importObject);
+ }
+
+ return importObjects;
+ },
+
+ getSingleImportTargetId: function()
+ {
+ let targetId = null;
+
+ const importObjects = this.getImports();
+ if (importObjects.length > 0)
+ {
+ targetId = importObjects[0].getTargetId();
+ }
+
+ return targetId;
+ }
+
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.TransferImportJob = Gitana.Job.extend(
+ /** @lends Gitana.TransferImportJob.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class TransferImportJob
+ *
+ * @param {Gitana.Cluster} cluster
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(cluster, object)
+ {
+ this.base(cluster, object);
+
+ this.objectType = function() { return "Gitana.TransferImportJob"; };
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return new Gitana.TransferExportJob(this.getCluster(), this);
+ },
+
+ getImports: function()
+ {
+ const importObjects = [];
+
+ const array = this.get("imports");
+ for (let i = 0; i < array.length; i++)
+ {
+ const object = array[i];
+
+ const sources = object["sources"];
+ const targets = object["targets"];
+
+ const importObject = {
+ sources,
+ targets,
+ getType: function()
+ {
+ return this.targets[this.targets.length - 1]["typeId"];
+ },
+ getSourceId: function()
+ {
+ return this.sources[this.sources.length - 1]["id"];
+ },
+ getTargetId: function()
+ {
+ return this.targets[this.targets.length - 1]["id"];
+ }
+ };
+ importObjects.push(importObject);
+ }
+
+ return importObjects;
+ },
+
+ getSingleImportTargetId: function()
+ {
+ let targetId = null;
+
+ const importObjects = this.getImports();
+ if (importObjects.length > 0)
+ {
+ targetId = importObjects[0].getTargetId();
+ }
+
+ return targetId;
+ }
+ });
+
+})(window);
+(function(window)
+{
+ Gitana = window.Gitana;
+
+ Gitana.TransferExportJob = Gitana.Job.extend(
+ /** @lends Gitana.TransferExportJob.prototype */
+ {
+ /**
+ * @constructs
+ * @augments Gitana.AbstractObject
+ *
+ * @class TransferExportJob
+ *
+ * @param {Gitana.Cluster} cluster
+ * @param {Object} object json object (if no callback required for populating)
+ */
+ constructor: function(cluster, object)
+ {
+ this.base(cluster, object);
+
+ this.objectType = function() { return "Gitana.TransferExportJob"; };
+ },
+
+ /**
+ * @override
+ */
+ clone: function()
+ {
+ return new Gitana.TransferExportJob(this.getCluster(), this);
+ },
+
+ getImports: function()
+ {
+ const importObjects = [];
+
+ const array = this.get("imports");
+ for (let i = 0; i < array.length; i++)
+ {
+ const object = array[i];
+
+ const sources = object["sources"];
+ const targets = object["targets"];
+
+ const importObject = {
+ sources,
+ targets,
+ getType: function()
+ {
+ return this.targets[this.targets.length - 1]["typeId"];
+ },
+ getSourceId: function()
+ {
+ return this.sources[this.sources.length - 1]["id"];
+ },
+ getTargetId: function()
+ {
+ return this.targets[this.targets.length - 1]["id"];
+ }
+ };
+ importObjects.push(importObject);
+ }
+
+ return importObjects;
+ },
+
+ getSingleImportTargetId: function()
+ {
+ let targetId = null;
+
+ const importObjects = this.getImports();
+ if (importObjects.length > 0)
+ {
+ targetId = importObjects[0].getTargetId();
+ }
+
+ return targetId;
+ }
+ });
+
+})(window);
+(function(window)
+{
+ 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()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri() + "/";
+ };
+
+ return this.chainReload(null, uriFunction);
+ },
+
+ /** @Override **/
+ update: function()
+ {
+ const uriFunction = function()
+ {
+ return this.getUri() + "/";
+ };
+
+ return this.chainUpdate(null, uriFunction);
+ },
+
+ /**
+ * Hands back the primary domain instance for this platform.
+ *
+ * @chained domain
+ */
+ readPrimaryDomain: function()
+ {
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri() + "/domains/primary";
+ };
+
+ const chainable = this.getFactory().domain(this);
+ return this.chainGet(chainable, uriFunction);
+ },
+
+ /**
+ * Loads information about the platform.
+ *
+ * @param callback
+ */
+ loadInfo: function(callback)
+ {
+ const 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)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const chainable = this.getFactory().repository(this);
+ return this.chainGet(chainable, "/repositories/" + repositoryId);
+ },
+
+ /**
+ * Create a repository
+ *
+ * @chained repository
+ *
+ * @param {Object} object JSON object
+ */
+ createRepository: function(object)
+ {
+ const 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)
+ {
+ const chainable = this.getFactory().repositoryMap(this);
+
+ // prepare params (with pagination)
+ const 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": "",
+ * "domainId": "", (optional)
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "domainId": "", (optional)
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/repositories/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type repository.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "domainId": "", (optional)
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "domainId": "", (optional)
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkRepositoryAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/repositories/authorities/check";
+ };
+
+ const 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)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const chainable = this.getFactory().domain(this);
+ return this.chainGet(chainable, "/domains/" + domainId);
+ },
+
+ /**
+ * Create a domain
+ *
+ * @chained domain
+ *
+ * @param {Object} object JSON object
+ */
+ createDomain: function(object)
+ {
+ const 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)
+ {
+ const chainable = this.getFactory().domainMap(this);
+
+ // prepare params (with pagination)
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/domains/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of permissions against permissioned objects of type domain.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkDomainAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/domains/authorities/check";
+ };
+
+ const 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)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const chainable = this.getFactory().vault(this);
+ return this.chainGet(chainable, "/vaults/" + vaultId);
+ },
+
+ /**
+ * Create a vault
+ *
+ * @chained vault
+ *
+ * @param {Object} object JSON object
+ */
+ createVault: function(object)
+ {
+ const 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)
+ {
+ const chainable = this.getFactory().vaultMap(this);
+
+ // prepare params (with pagination)
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/vaults/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type vault.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkVaultAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/vaults/authorities/check";
+ };
+
+ const 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} authFailureHandler 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)
+ {
+ return this.subchain().then(function() {
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().stackMap(this);
+ return this.chainGet(chainable, "/stacks", params);
+ },
+
+ /**
+ * Reads a stack.
+ *
+ * @param stackId
+ *
+ * @chained stack
+ */
+ readStack: function(stackId)
+ {
+ const 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 = {};
+ }
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/stacks/query";
+ };
+
+ const 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)
+ {
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/stacks/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type stack.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkStackAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/stacks/authorities/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // PROJECTS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Lists the projects.
+ *
+ * @param pagination
+ *
+ * @chained project map
+ */
+ listProjects: function(pagination)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().projectMap(this);
+ return this.chainGet(chainable, "/projects", params);
+ },
+
+ /**
+ * Reads a project.
+ *
+ * @param projectId
+ *
+ * @chained project
+ */
+ readProject: function(projectId)
+ {
+ const 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 = {};
+ }
+
+ const 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
+ * @param {Object} params request parameters
+ * @param callback
+ */
+ startCreateProject: function(object, params, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/projects/start";
+ };
+
+ if (!object)
+ {
+ object = {};
+ }
+
+ if (!params)
+ {
+ params = {};
+ }
+
+ return this.chainPostResponse(this, uriFunction, params, object).then(function(response) {
+
+ const jobId = response._doc;
+
+ callback(jobId);
+ });
+ },
+
+ /**
+ * Queries for projects.
+ *
+ * @chained project map
+ *
+ * @param {Object} query
+ * @param {Object} pagination pagination (optional)
+ */
+ queryProjects: function(query, pagination)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/projects/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/projects/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type project.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkProjectAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/projects/authorities/check";
+ };
+
+ const 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)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri() + "/logs/query";
+ };
+
+ if (!query)
+ {
+ query = {};
+ }
+
+ const chainable = this.getFactory().logEntryMap(this.getCluster());
+
+ // prepare params (with pagination)
+ const 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)
+ {
+ const self = this;
+
+ const uriFunction = function()
+ {
+ return self.getUri() + "/logs/" + logEntryId;
+ };
+
+ const 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)
+ {
+ const 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)
+ {
+
+ if (!obj) {
+ obj = {};
+ }
+ obj.message = message;
+ obj.level = level;
+
+ const 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)
+ {
+ const self = this;
+
+ const 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)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const chainable = this.getFactory().registrar(this);
+ return this.chainGet(chainable, "/registrars/" + registrarId);
+ },
+
+ /**
+ * Create a registrar
+ *
+ * @chained registrar
+ *
+ * @param {Object} object JSON object
+ */
+ createRegistrar: function(object)
+ {
+ const 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)
+ {
+ const chainable = this.getFactory().registrarMap(this);
+
+ // prepare params (with pagination)
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/registrars/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type vault.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkRegistrarAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/registrars/authorities/check";
+ };
+
+ const 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)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const uriFunction = function() {
+ return "/applications/" + applicationId;
+ };
+
+ const chainable = this.getFactory().application(this);
+ return this.chainGet(chainable, uriFunction);
+ },
+
+ /**
+ * Create an application
+ *
+ * @chained application
+ *
+ * @param {Object} object JSON object
+ */
+ createApplication: function(object)
+ {
+ const 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)
+ {
+ const chainable = this.getFactory().applicationMap(this);
+
+ // prepare params (with pagination)
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/applications/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type vault.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkApplicationAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/applications/authorities/check";
+ };
+
+ const 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)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().applicationMap(this);
+ return this.chainGet(chainable, "/applicationtypes", params);
+ },
+
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // CLIENTS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Lists the clients.
+ *
+ * @param pagination
+ *
+ * @chained client map
+ */
+ listClients: function(pagination)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().clientMap(this);
+ return this.chainGet(chainable, "/clients", params);
+ },
+
+ /**
+ * Reads a client.
+ *
+ * @param clientId
+ *
+ * @chained client
+ */
+ readClient: function(clientId)
+ {
+ const 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 = {};
+ }
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/clients/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/clients/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type client.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkClientAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/clients/authorities/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ACCESS POLICIES
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Reads an access policy.
+ *
+ * @param {String} accessPolicyId
+ *
+ * @chained accessPolicy
+ */
+ readAccessPolicy: function(accessPolicyId)
+ {
+ const chainable = this.getFactory().accessPolicy(this);
+ return this.chainGet(chainable, "/access/policies/" + accessPolicyId);
+ },
+
+ /**
+ * Create an Access Policy
+ *
+ * @param {Object} object
+ *
+ * @chained accessPolicy
+ */
+ createAccessPolicy: function(object)
+ {
+ if (!object)
+ {
+ object = {};
+ }
+
+ const chainable = this.getFactory().accessPolicy(this);
+ return this.chainCreate(chainable, object, "/access/policies");
+ },
+
+ /**
+ * List all Access Policies in the platform.
+ *
+ * @param {Object} pagination
+ *
+ * @chained accessPolicyMap
+ */
+ listAccessPolicies: function(pagination)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ params.clientId = this.getId();
+
+ const chainable = this.getFactory().accessPolicyMap(this.getPlatform());
+ return this.chainGet(chainable, "/access/policies", params);
+ },
+
+ /**
+ * Query for Access Policies.
+ *
+ * @param {Object} query
+ * @param {Object} pagination
+ *
+ * @chained accessPolicyMap
+ */
+ queryAccessPolicies: function(query, pagination)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().accessPolicyMap(this.getPlatform());
+ return this.chainPost(chainable, "/access/policies/query", params, query);
+ },
+
+ /**
+ * Finds Access Policies bound to a particular resource.
+ *
+ * @param {String} ref
+ * @param {Object} pagination
+ *
+ * @chained accessPolicyMap
+ */
+ findAccessPolicies: function(ref, pagination)
+ {
+ const params = {};
+ params.ref = ref;
+
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().accessPolicyMap(this.getPlatform());
+ return this.chainPost(chainable, "/access/policies/find", params);
+ },
+
+ /**
+ * Lists target documents bound to a particular Access Policy.
+ *
+ * @param {String} accessPolicyId
+ * @param {Object} pagination
+ */
+ listAccessPolicyTargets: function(accessPolicyId, pagination)
+ {
+ const params = {};
+ params.accessPolicyId = accessPolicyId;
+
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uri = "/access/policies/" + accessPolicyId + "/targets";
+
+ const chainable = this.getFactory().accessPolicyMap(this.getPlatform());
+ return this.chainGet(chainable, uri, params);
+ },
+
+ /**
+ * Assign an access policy to a particular resource.
+ *
+ * @param {String} accessPolicyId
+ * @param {String} ref
+ */
+ assignAccessPolicy: function(accessPolicyId, ref)
+ {
+ const params = {};
+ params.ref = ref;
+
+ const uri = "/access/policies/" + accessPolicyId + "/assign";
+
+ const chainable = this.getFactory().accessPolicyMap(this.getPlatform());
+ return this.chainPost(chainable, uri, params);
+ },
+
+ /**
+ * Unassign an access policy from a particular resource.
+ *
+ * @param {String} accessPolicyId
+ * @param {String} ref
+ */
+ unassignAccessPolicy: function(accessPolicyId, ref)
+ {
+ const params = {};
+ params.ref = ref;
+
+ const uri = "/access/policies/" + accessPolicyId + "/unassign";
+
+ const chainable = this.getFactory().accessPolicyMap(this.getPlatform());
+ return this.chainPost(chainable, uri, params);
+ },
+
+ /**
+ * Unassign all access policies from a particular resource.
+ *
+ * @param {String} ref
+ */
+ unassignAllAccessPolicies: function(ref)
+ {
+ const params = {};
+ params.ref = ref;
+
+ const chainable = this.getFactory().accessPolicyMap(this.getPlatform());
+ return this.chainPost(chainable, "/access/policies/unassignall", params);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // AUTHENTICATION GRANTS
+ //
+ //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Reads an authentication grant.
+ *
+ * @param authenticationGrantId
+ *
+ * @chained authentication grant
+ */
+ readAuthenticationGrant: function(authenticationGrantId)
+ {
+ const 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 = {};
+ }
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/auth/grants/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/auth/grants/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type authentication grant.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkAuthenticationGrantAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/auth/grants/authorities/check";
+ };
+
+ const 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)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const chainable = this.getFactory().directory(this);
+ return this.chainGet(chainable, "/directories/" + directoryId);
+ },
+
+ /**
+ * Create a directory.
+ *
+ * @chained directory
+ *
+ * @param {Object} object JSON object
+ */
+ createDirectory: function(object)
+ {
+ const 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)
+ {
+ const chainable = this.getFactory().directoryMap(this);
+
+ // prepare params (with pagination)
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/directories/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type directory.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkDirectoryAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/directories/authorities/check";
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const 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;
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/billing/configurations/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/billing/configurations/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type billing provider configuration.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkBillingProviderConfigurationAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/billing/configurations/authorities/check";
+ };
+
+ const 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)
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const 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)
+ {
+ const 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)
+ {
+ const chainable = this.getFactory().webhostMap(this);
+
+ // prepare params (with pagination)
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/webhosts/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type web host.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkWebHostAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/webhosts/authorities/check";
+ };
+
+ const 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()
+ {
+ const self = this;
+
+ // we bind the attachment map to a modified copy of platform with the URI adjusted
+ // so that it forms "/tenant/attachments/" for any lookups
+ const pseudoTenant = this.clone();
+ pseudoTenant.getUri = function () {
+ return "/tenant";
+ };
+
+ const result = this.subchain(new Gitana.BinaryAttachmentMap(pseudoTenant));
+ result.then(function() {
+
+ const 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)
+ {
+ const self = this;
+
+ const tenant = this.clone();
+ tenant.getUri = function () {
+ return "/tenant";
+ };
+
+ // the thing we're handing back
+ const result = this.subchain(new Gitana.BinaryAttachment(tenant));
+
+ // preload some work onto a subchain
+ result.subchain().then(function() {
+
+ // upload the attachment
+ const 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)
+ {
+ const self = this;
+
+ // support for null appkey
+ if (Gitana.isFunction(settings)) {
+ callback = settings;
+ settings = null;
+ }
+
+ if (Gitana.isString(settings)) {
+ settings = { "application": settings };
+ }
+
+ // build preload config
+ const 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?
+ const cacheKey = config.appCacheKey;
+ if (cacheKey)
+ {
+ if (Gitana.APPS && Gitana.APPS[cacheKey])
+ {
+ return callback.call(Chain(Gitana.APPS[cacheKey]));
+ }
+ }
+
+ if (!config.application) {
+ callback.call(self, new Error("No application configured"));
+ return;
+ }
+
+ // load and cache
+ const 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": "",
+ * "principalId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissioned": "",
+ * "principalId": "",
+ * "authorities": [...],
+ * "permissions": [...]
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param entries
+ * @param callback
+ */
+ accessLookups: function(entries, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/access/lookup";
+ };
+
+ const 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": "",
+ * "principalId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissioned": "",
+ * "principalId": "",
+ * "permissionId|authorityId": "",
+ * "hasPermission|hasAuthority": true | false
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param entries
+ * @param callback
+ */
+ accessChecks: function(entries, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/access/check";
+ };
+
+ const 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": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "ref": "",
+ * "entry": { ... object }
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param entries
+ * @param callback
+ */
+ referenceReads: function(entries, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/ref/read";
+ };
+
+ const 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)
+ {
+ const uriFunction = function()
+ {
+ return "/ref/diff";
+ };
+
+ const 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)
+ {
+ const uriFunction = function()
+ {
+ return "/ref/merge";
+ };
+
+ const params = {
+ "source": sourceRef
+ };
+
+ return this.chainPostResponse(this, uriFunction, params, diffObject).then(function(response) {
+ callback.call(this, response);
+ });
+ },
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // ADMIN
+ //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ adminIndexDatastores: function()
+ {
+ const self = this;
+
+ return this.then(function() {
+
+ const chain = this;
+
+ // call
+ const uri = self.getUri() + "/admin/index";
+ self.getDriver().gitanaPost(uri, null, {}, function() {
+ chain.next();
+ });
+
+ // NOTE: we return false to tell the chain that we'll manually call next()
+ return false;
+ });
+ },
+
+ adminRepair: function()
+ {
+ const self = this;
+
+ return this.then(function() {
+
+ const chain = this;
+
+ // call
+ const uri = self.getUri() + "/admin/repair";
+ self.getDriver().gitanaPost(uri, null, {}, function() {
+ 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
+ *
+ * @param callback
+ * @chained action descriptor map
+ */
+ listRuleActions: function(pagination, callback)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/rule/actions";
+ };
+
+ return this.chainGetResponse(this, uriFunction, params).then(function(response) {
+ callback(response);
+ });
+ },
+
+ /**
+ * Reads a rule action.
+ *
+ *
+ * @chained a
+ * @param actionId
+ * @param callback
+ */
+ readRuleAction: function(actionId, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/rule/actions/" + actionId;
+ };
+
+ return this.chainGetResponse(this, uriFunction, {}).then(function(response) {
+ callback(response);
+ });
+ },
+
+ /**
+ * Lists the available rule conditions.
+ *
+ * @param pagination
+ *
+ * @param callback
+ * @chained condition descriptor map
+ */
+ listRuleConditions: function(pagination, callback)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/rule/conditions";
+ };
+
+ return this.chainGetResponse(this, uriFunction, params).then(function(response) {
+ callback(response);
+ });
+ },
+
+ /**
+ * Reads a rule condition.
+ *
+ * @param conditionId
+ *
+ * @param callback
+ * @chained a
+ */
+ readRuleCondition: function(conditionId, callback)
+ {
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const uriFunction = function()
+ {
+ let url = "/workflow/models/" + workflowModelId;
+ if (workflowModelVersionId)
+ {
+ url += "/versions/" + workflowModelVersionId;
+ }
+
+ return url;
+ };
+
+ const 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;
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/workflow/models/query";
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/workflow/models/query?all=true";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/workflow/models/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type workflow model.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkWorkflowModelAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/workflow/models/authorities/check";
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/workflow/models/" + id + "/versions";
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+
+ return "/workflow/models/" + id + "/versions/query";
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().workflowInstanceMap(this);
+ return this.chainGet(chainable, "/workflow/instances", params);
+ },
+
+ /**
+ * Reads a workflow.
+ *
+ * @param workflowId
+ *
+ * @chained workflow
+ */
+ readWorkflow: function(workflowId)
+ {
+ const 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 = {};
+ }
+
+ const params = {
+ "modelId": workflowModelId
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/workflow/instances/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/workflow/instance/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type workflow instance.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkWorkflowInstanceAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/workflow/instance/authorities/check";
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().workflowTaskMap(this);
+ return this.chainGet(chainable, "/workflow/tasks", params);
+ },
+
+ /**
+ * Reads a workflow task.
+ *
+ * @param workflowTaskId
+ *
+ * @chained workflow task
+ */
+ readWorkflowTask: function(workflowTaskId)
+ {
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/workflow/tasks/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/workflow/task/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type workflow task.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkWorkflowTaskAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/workflow/task/authorities/check";
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().workflowCommentMap(this);
+ return this.chainGet(chainable, "/workflow/comments", params);
+ },
+
+ /**
+ * Reads a workflow comment.
+ *
+ * @param workflowCommentId
+ *
+ * @chained workflow comment
+ */
+ readWorkflowComment: function(workflowCommentId)
+ {
+ const 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)
+ {
+
+ const createUri = function()
+ {
+ let uri = "/workflow/instances/" + workflowId + "/comments";
+ if (workflowTaskId)
+ {
+ uri += "?taskId=" + workflowTaskId;
+ }
+
+ return uri;
+ };
+
+ const readUri = function(status)
+ {
+ return "/workflow/comments/" + status._doc;
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/workflow/comments/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/workflow/comments/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type workflow task.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkWorkflowCommentAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/workflow/comments/authorities/check";
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ if (filter)
+ {
+ params.filter = filter;
+ }
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ if (filter)
+ {
+ params.filter = filter;
+ }
+
+ const chainable = this.getFactory().workflowTaskMap(this);
+ return this.chainPost(chainable, "/workflow/user/tasks/query", params, query);
+ },
+
+ queryTasks: function(query, pagination)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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 pagination
+ * @param callback
+ */
+ loadWorkflowHistory: function(workflowId, pagination, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/workflow/instances/" + workflowId + "/history";
+ };
+
+ // prepare params (with pagination)
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const 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)
+ {
+ const 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 = {};
+ }
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/work/scheduled/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/work/scheduled/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type scheduled work.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkScheduledWorkAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/work/scheduled/authorities/check";
+ };
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().reportMap(this);
+ return this.chainGet(chainable, "/reports", params);
+ },
+
+ /**
+ * Reads a report.
+ *
+ * @param reportId
+ *
+ * @chained report
+ */
+ readReport: function(reportId)
+ {
+ const 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 = {};
+ }
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/reports/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/reports/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type report.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "",
+ * "result": true
+ * }]
+ *
+ * The order of elements in the array will be the same for checks and results.
+ *
+ * @param checks
+ * @param callback
+ */
+ checkReportAuthorities: function(checks, callback)
+ {
+ const uriFunction = function()
+ {
+ return "/reports/authorities/check";
+ };
+
+ const 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 {Object} pagination
+ * @param {Function} callback callback to fire
+ */
+ executeReport: function(reportId, config, pagination, callback)
+ {
+ if (typeof(config) === "function")
+ {
+ callback = config;
+ config = {};
+ pagination = null;
+ }
+
+ if (typeof(pagination) === "function")
+ {
+ callback = pagination;
+ pagination = null;
+
+ if (!config) {
+ config = {};
+ }
+ }
+
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/reports/" + reportId + "/execute";
+ };
+
+ return this.chainPostResponse(this, uriFunction, params, 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)
+ {
+ const uriFunction = function()
+ {
+ return "/ref/exports/start";
+ };
+
+ if (!configuration)
+ {
+ configuration = {};
+ }
+
+ let references = [];
+ if (objects.refs)
+ {
+ references = objects.refs();
+ }
+ else if (objects.length)
+ {
+ for (let i = 0; i < objects.length; i++)
+ {
+ references.push(objects[i].ref());
+ }
+ }
+ configuration.references = references;
+
+ const chainable = this;
+
+ return this.chainPostResponse(this, uriFunction, {}, configuration).then(function(response) {
+
+ const exportId = response._doc;
+
+ // wait for the export to finish...
+ const 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)
+ {
+ const 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)
+ {
+ let 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const chainable = this.getFactory().descriptorMap(this);
+ return this.chainGet(chainable, "/descriptors", params);
+ },
+
+ /**
+ * Reads a service descriptor.
+ *
+ * @param descriptorId
+ *
+ * @chained descriptor
+ */
+ readServiceDescriptor: function(descriptorId)
+ {
+ const 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 = {};
+ }
+
+ const 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)
+ {
+ const params = {};
+ if (pagination)
+ {
+ Gitana.copyInto(params, pagination);
+ }
+
+ const uriFunction = function()
+ {
+ return "/descriptors/query";
+ };
+
+ const 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": "",
+ * "principalId": "",
+ * "permissionId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "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)
+ {
+ const uriFunction = function()
+ {
+ return "/descriptors/permissions/check";
+ };
+
+ const object = {
+ "checks": checks
+ };
+
+ return this.chainPostResponse(this, uriFunction, {}, object).then(function(response) {
+ callback.call(this, response["results"]);
+ });
+ },
+
+ /**
+ * Performs a bulk check of authorities against permissioned objects of type external service descriptor.
+ *
+ * Example of checks array:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": ""
+ * }]
+ *
+ * The callback receives an array of results, example:
+ *
+ * [{
+ * "permissionedId": "",
+ * "principalId": "",
+ * "authorityId": "