(function(window)
{
var Gitana = window.Gitana;
Gitana.Node = Gitana.AbstractNode.extend(
/** @lends Gitana.Node.prototype */
{
/**
* @constructs
* @augments Gitana.AbstractNode
*
* @class Node
*
* @param {Gitana.Branch} branch
* @param {Object} [object] json object (if no callback required for populating)
*/
constructor: function(branch, object)
{
this.base(branch, object);
this.objectType = function() { return "Gitana.Node"; };
},
/**
* @override
*/
getType: function()
{
return Gitana.TypedIDConstants.TYPE_NODE;
},
/**
* Acquires the "child nodes" of this node. This is done by fetching all of the nodes that are outgoing-associated to this
* node with a association of type "a:child".
*
* @chained node map
*
* @public
*
* @param {object} [pagination]
*/
listChildren: function(pagination)
{
var params = {};
if (pagination)
{
Gitana.copyInto(params, pagination);
}
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/children";
};
var chainable = this.getFactory().nodeMap(this.getBranch());
return this.chainGet(chainable, uriFunction, params);
},
/**
* Acquires the relatives of this node.
*
* @chained node map
*
* @public
*
* @param {Object} config
* @param {Object} [pagination]
*/
listRelatives: function(config, pagination)
{
var type = null;
var direction = null;
if (config)
{
type = config.type;
if (config.direction)
{
direction = config.direction.toUpperCase();
}
}
var params = {};
if (pagination)
{
Gitana.copyInto(params, pagination);
}
var uriFunction = function()
{
var url = "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/relatives";
if (type)
{
url = url + "?type=" + type;
}
if (direction)
{
if (type)
{
url = url + "&direction=" + direction;
}
else
{
url = url + "?direction=" + direction;
}
}
return url;
};
var chainable = this.getFactory().nodeMap(this.getBranch());
return this.chainGet(chainable, uriFunction, params);
},
/**
* Queries for relatives of this node.
*
* @chained node map
*
* @public
*
* @param {Object} query
* @param {Object} [config]
* @param {Object} [pagination]
*/
queryRelatives: function(query, config, pagination)
{
var type = null;
var direction = null;
if (config)
{
type = config.type;
if (config.direction)
{
direction = config.direction.toUpperCase();
}
}
var params = {};
if (pagination)
{
Gitana.copyInto(params, pagination);
}
var uriFunction = function()
{
var url = "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/relatives/query";
if (type)
{
url = url + "?type=" + type;
}
if (direction)
{
if (type)
{
url = url + "&direction=" + direction;
}
else
{
url = url + "?direction=" + direction;
}
}
return url;
};
var chainable = this.getFactory().nodeMap(this.getBranch());
return this.chainPost(chainable, uriFunction, params, query);
},
/**
* Retrieves all of the association objects for this node.
*
* @chained node map
*
* @public
*
* @param {Object} config
* @param {Object} pagination
*/
associations: function(config, pagination)
{
var type = null;
var direction = null;
if (config)
{
type = config.type;
if (config.direction)
{
direction = config.direction.toUpperCase();
}
}
var params = {};
if (pagination)
{
Gitana.copyInto(params, pagination);
}
var uriFunction = function()
{
var url = "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/associations?a=1";
if (type)
{
url = url + "&type=" + type;
}
if (direction)
{
url = url + "&direction=" + direction;
}
return url;
};
var chainable = this.getFactory().nodeMap(this.getBranch());
return this.chainGet(chainable, uriFunction, params);
},
/**
* Retrieves all of the incoming association objects for this node.
*
* @chained node map
*
* @public
*
* @param {String} [type] - the type of association
* @param {Object} [pagination]
*/
incomingAssociations: function(type, pagination)
{
var config = {
"direction": "INCOMING"
};
if (type) {
config.type = type;
}
return this.associations(config, pagination);
},
/**
* Retrieves all of the outgoing association objects for this node.
*
* @chained node map
*
* @public
*
* @param {String} [type] the type of association
* @param {Object} [pagination]
*/
outgoingAssociations: function(type, pagination)
{
var config = {
"direction": "OUTGOING"
};
if (type) {
config.type = type;
}
return this.associations(config, pagination);
},
/**
* Associates a target node to this node.
*
* @chained this
*
* @public
*
* @param {String|Node} targetNode - the id of the target node or the target node itself
* @param {Object|String} [object] either a JSON object or a string identifying the type of association
* @param {Boolean} [undirected] whether the association is undirected (i.e. mutual)
*/
associate: function(targetNodeId, object, undirected)
{
if (!Gitana.isString(targetNodeId))
{
targetNodeId = targetNodeId.getId();
}
if (object)
{
if (Gitana.isString(object))
{
object = {
"_type": object
};
}
}
var uriFunction = function()
{
var url = "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/associate?node=" + targetNodeId;
if (undirected)
{
url += "&directionality=UNDIRECTED";
}
return url;
};
return this.chainPostEmpty(null, uriFunction, null, object);
},
/**
* Creates an association from another node to this one.
*
* @chained node (this)
*
* @public
*
* @param {Node} sourceNode
* @param {Object} object
* @param {Boolean} [undirected]
*/
associateOf: function(sourceNode, object, undirected)
{
var self = this;
// what we're handing back (ourselves)
var result = this.subchain(this);
// our work
result.subchain(sourceNode).then(function() {
this.associate(self, object, undirected);
});
return result;
},
/**
* Unassociates a target node from this node.
*
* @chained this
*
* @public
*
* @param {String|Node} targetNode the id of the target node or the target node itself
* @param {String} [type] A string identifying the type of association
* @param {Boolean} [undirected] whether the association is undirected (i.e. mutual)
*/
unassociate: function(targetNodeId, type, undirected)
{
if (!Gitana.isString(targetNodeId))
{
targetNodeId = targetNodeId.getId();
}
var uriFunction = function()
{
var url = "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/unassociate?node=" + targetNodeId;
if (type)
{
url = url + "&type=" + type;
}
if (undirected)
{
url += "&directionality=UNDIRECTED";
}
return url;
};
return this.chainPostEmpty(null, uriFunction);
},
/**
* Traverses around the node and returns any nodes found to be connected on the graph.
*
* Example config:
*
* {
* "associations": {
* "a:child": "MUTUAL",
* "a:knows": "INCOMING",
* "a:related": "OUTGOING"
* },
* "depth": 1,
* "types": [ "custom:type1", "custom:type2" ]
* }
*
* @chained traversal results
*
* @public
*
* @param {Object} config configuration for the traversal
*/
traverse: function(config)
{
// build the payload
var payload = {
"traverse": config
};
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/traverse";
};
var chainable = this.getFactory().traversalResults(this.getBranch());
var params = {};
return this.chainPost(chainable, uriFunction, params, payload);
},
/**
* Mounts a node
*
* @chained this
*
* @public
*
* @param {String} mountKey the mount key
*/
mount: function(mountKey)
{
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/mount/" + mountKey;
};
return this.chainPostEmpty(null, uriFunction, null, object);
},
/**
* Unmounts a node
*
* @public
*/
unmount: function()
{
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/unmount";
};
return this.chainPostEmpty(null, uriFunction, null, object);
},
/**
* Locks a node
*
* @chained this
*
* @public
*/
lock: function()
{
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/lock";
};
return this.chainPostEmpty(null, uriFunction);
},
/**
* Unlocks a node
*
* @chained this
*
* @public
*/
unlock: function()
{
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/unlock";
};
return this.chainPostEmpty(null, uriFunction);
},
/**
* Checks whether the node is locked.
* The result is passed into the next method in the chain.
*
* @chained this
*
* @public
*/
checkLocked: function(callback)
{
// TODO: isn't this subchain() redundant?
return this.subchain(this).then(function() {
var chain = this;
// call
var uri = "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/lock";
this.getDriver().gitanaGet(uri, null, {}, function(response) {
callback.call(chain, response["locked"]);
chain.next();
});
// NOTE: we return false to tell the chain that we'll manually call next()
return false;
});
},
//////////////////////////////////////////////////////////////////////////////////////////
//
// ACL METHODS
//
//////////////////////////////////////////////////////////////////////////////////////////
/**
* Retrieve full ACL and pass into chaining method.
*
* @chained node
*/
loadACL: function(callback)
{
var self = this;
var 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 node
*
* @param {Gitana.Principal|String} principal the principal or the principal id
*/
listAuthorities: function(principal)
{
var principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/acl?id=" + principalDomainQualifiedId;
};
return this.chainGetResponseRows(this, uriFunction);
},
/**
* 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 {Function} callback
*/
checkAuthority: function(principal, authorityId, callback)
{
var principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/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)
{
var principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/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)
{
var principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/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 repository
*
* @param {Array} principalIds
* @param {Function} callback
*/
loadAuthorityGrants: function(principalIds, callback)
{
if (!principalIds)
{
principalIds = [];
}
var json = {
"principals": principalIds
};
return this.chainPostResponse(this, "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/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 server
*
* @param {Gitana.Principal|String} principal the principal or the principal id
* @param {String} permissionId the id of the permission
* @param {Function} callback
*/
checkPermission: function(principal, permissionId, callback)
{
var principalDomainQualifiedId = this.extractPrincipalDomainQualifiedId(principal);
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/permissions/" + permissionId + "/check?id=" + principalDomainQualifiedId;
};
return this.chainPostResponse(this, uriFunction).then(function(response) {
callback.call(this, response["check"]);
});
},
//////////////////////////////////////////////////////////////////////////////////////////
//
// END OF ACL METHODS
//
//////////////////////////////////////////////////////////////////////////////////////////
/**
* Acquire a list of audit records concerning this node.
*
* @chained audit record map
*
* @public
*
* @param {Object} [pagination] pagination
*/
listAuditRecords: function(pagination)
{
var params = {};
if (pagination)
{
Gitana.copyInto(params, pagination);
}
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/auditrecords";
};
var chainable = this.getFactory().auditRecordMap(this.getRepository());
return this.chainGet(chainable, uriFunction, params);
},
/**
* Creates a new translation.
*
* @chained translation node
*
* @param {String} edition the edition of the translation (can be any string)
* @param {String} locale the locale string for the translation (i.e. "en_US")
* @param {Object} [object] JSON object
*/
createTranslation: function(edition, locale, object)
{
var uriFunction = function()
{
var url = "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/i18n?locale=" + locale;
if (edition)
{
url += "&edition=" + edition;
}
return url;
};
var chainable = this.getFactory().node(this.getBranch());
return this.chainCreateEx(chainable, object, uriFunction, uriFunction);
},
/**
* Lists all of the editions for this master node.
* Passes them into the next function in the chain.
*
* @chained this
*
* @param {function} callback
*/
editions: function(callback)
{
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/i18n/editions";
};
return this.chainGetResponse(this, uriFunction).then(function(response) {
callback.call(this, response["editions"]);
});
},
/**
* Lists all of the locales for the given edition of this master node.
* Passes them into the next function in the chain.
*
* @chained this
*
* @param {String} edition the edition
* @param {function} callback
*/
locales: function(edition, callback)
{
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/i18n/locales?edition=" + edition;
};
return this.chainGetResponse(this, uriFunction).then(function(response) {
callback.call(this, response["locales"]);
});
},
/**
* Acquires all of the translations for a master node.
*
* @chained node map
*
* @public
*
* @param {String} edition
* @param {Object} [pagination]
*/
listTranslations: function(edition, pagination)
{
var params = {};
if (edition)
{
params.edition = edition;
}
if (pagination)
{
Gitana.copyInto(params, pagination);
}
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/i18n/translations";
};
var chainable = this.getFactory().nodeMap(this.getBranch());
return this.chainGet(chainable, uriFunction, params);
},
/**
* Reads a translation node of the current master node into a given locale and optional edition.
* If an edition isn't provided, the tip edition from the master node is assumed.
*
* @chained translation node
*
* @param {String} [edition] The edition of the translation to use. If not provided, the tip edition is used from the master node.
* @param {String} locale The locale to translate into.
*/
readTranslation: function()
{
var edition;
var locale;
var args = Gitana.makeArray(arguments);
if (args.length == 1)
{
locale = args.shift();
}
else if (args.length > 1)
{
edition = args.shift();
locale = args.shift();
}
var uriFunction = function()
{
var uri = "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/i18n?locale=" + locale;
if (edition)
{
uri += "&edition=" + edition;
}
return uri;
};
var chainable = this.getFactory().node(this.getBranch());
return this.chainGet(chainable, uriFunction);
},
//////////////////////////////////////////////////////////////////////////////////////////
//
// CONTAINER CONVENIENCE FUNCTIONS
//
//////////////////////////////////////////////////////////////////////////////////////////
/**
* Create a node as a child of this node.
*
* This is a convenience function around the branch createNode method. It chains a create with a
* childOf() call.
*
* @chained new node
*
* @public
*
* @param {Object} [object] JSON object
*/
createChild: function(object)
{
var self = this;
// we can't assume we know the branch get since we're chaining
// so create a temporary branch that we'll load later
var branch = new Gitana.Branch(this.getRepository());
// we hand back a node and preload some work
var chainable = this.getFactory().node(branch);
return this.subchain(chainable).then(function() {
var chain = this;
// we now plug in branch and create child node
this.subchain(self).then(function() {
// load branch
branch.loadFrom(this.getBranch());
// create child node
this.subchain(branch).createNode(object).then(function() {
chain.loadFrom(this);
this.childOf(self);
});
});
});
},
/**
* Associates this node as an "a:child" of the source node.
*
* This is a convenience function that simply creates an association from another node to this one.
*
* @chained node (this)
*
* @public
*
* @param {Node} sourceNode
*/
childOf: function(sourceNode)
{
return this.associateOf(sourceNode, "a:child");
},
//////////////////////////////////////////////////////////////////////////////////////////
//
// FIND
//
//////////////////////////////////////////////////////////////////////////////////////////
/**
* Finds around a node.
*
* @chained node map
*
* Config should be:
*
* {
* "query": {
* ... Query Block
* },
* "search": {
* ... Elastic Search Config Block
* },
* "traverse: {
* ... Traversal Configuration
* }
* }
*
* Alternatively, the value for "search" in the JSON block above can simply be text.
*
* @public
*
* @param {Object} config search configuration
* @param {Object} [pagination]
*/
find: function(config, pagination)
{
var params = {};
if (pagination)
{
Gitana.copyInto(params, pagination);
}
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/find";
};
var chainable = this.getFactory().nodeMap(this.getBranch());
return this.chainPost(chainable, uriFunction, params, config);
},
/**
* Retrieves a tree structure for nested folders starting at this node (as the root).
*
* @chained node
*
* @public
*
* @param {Object} config - { "leafPath": "<leafPath>", "basePath": "<basePath>", "containers": true, "depth": integer, "properties": true|false }
* @param {Function} callback - the callback function to be passed the resulting tree object structure
*/
loadTree: function(config, callback)
{
var self = this;
if (typeof(config) === "function")
{
callback = config;
config = null;
}
if (!config)
{
config = {};
}
var uriFunction = function()
{
return self.getUri() + "/tree";
};
var params = {};
if (config.leafPath)
{
params["leaf"] = config.leafPath;
}
if (config.basePath)
{
params["base"] = config.basePath;
}
if (config.containers)
{
params["containers"] = true;
}
if (config.properties)
{
params["properties"] = true;
}
params.depth = 1;
if (config.depth)
{
params["depth"] = config.depth;
}
return this.chainGetResponse(this, uriFunction, params).then(function(response) {
callback.call(this, response);
});
},
/**
* Resolves the path to this node relative to the given root node.
*
* @param {String} rootNodeId
* @param {Function} callback
* @returns {*}
*/
resolvePath: function(rootNodeId, callback)
{
var self = this;
var uriFunction = function()
{
return self.getUri() + "/path";
};
var params = {
"rootNodeId": rootNodeId
};
return this.chainGetResponse(this, uriFunction, params).then(function(response) {
callback.call(this, response.path);
});
},
////////////////////////////////////////
//
// VERSIONS
//
////////////////////////////////////////
listVersions: function(pagination)
{
var params = {};
if (pagination)
{
Gitana.copyInto(params, pagination);
}
var uriFunction = function () {
return this.getUri() + "/versions";
};
var chainable = this.getFactory().nodeMap(this.getBranch());
return this.chainGet(chainable, uriFunction, params);
},
restoreVersion: function(changesetId)
{
var uriFunction = function()
{
return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId() + "/versions/" + changesetId + "/restore";
};
var chainable = this.getFactory().node(this.getBranch());
return this.chainPost(chainable, uriFunction, {}, {});
}
});
})(window);