grex_naeem
Version:
Client for Rexster Graph Server
341 lines (287 loc) • 11.4 kB
JavaScript
var q = require("q"),
request = require("request");
var Utils = require("../utils"),
isObject = Utils.isObject,
addTypes = require("../addtypes");
module.exports = TransactionCommitter = (function() {
/*
* Constructor
*
* @param {Transaction} a transaction due for commit
*/
function TransactionCommitter(transaction) {
this.transaction = transaction;
this.inError = null;
}
/*
* Main commit method.
*
* @see Transaction#commit
*
* @return {Promise}
* @api public
*/
TransactionCommitter.prototype.doCommit = function() {
if(!!this.transaction.pendingVertices.length){
// We have new vertices to create first!
commit = this.commitVertices();
} else {
commit = this.commitEdges();
}
return commit;
};
/*
* Post vertices in batch to the database, eventually rolling them back
* in case of a failure.
*
* Internally calls updatePendingVertices() and, in the absence of a
* failure, calls updateEdges().
*
* @return {Promise}
* @api private
*/
TransactionCommitter.prototype.commitVertices = function() {
var self = this;
return this.getPostVerticesPromises().then(function(result){
self.updatePendingVertices(result);
if(this.inError){
return self.rollbackVertices()
.then(function(result){
throw result;
},function(error){
throw error;
});
}
//Update any edges that may have referenced the newly created Vertices
self.updateEdges();
return self.postBatch({ tx: self.transaction.txArray });
}, function(err){
console.error(err);
});
};
/*
* Post edges in batch to the database, replacing _inV and _outV
* references to Vertices as references to vertices _id.
*
* @return {Promise} of posting edges in batch.
* @api private
*/
TransactionCommitter.prototype.commitEdges = function() {
var transactionElement;
// We don't have new vertices to create, only edges...
for (var k = 0; k < this.transaction.txArray.length; k++) {
transactionElement = this.transaction.txArray[k];
if(transactionElement._type == 'edge' && transactionElement._action == 'create'){
if (isObject(transactionElement._inV)) {
transactionElement._inV = transactionElement._inV._id;
}
if (isObject(transactionElement._outV)) {
transactionElement._outV = transactionElement._outV._id;
}
}
}
return this.postBatch({ tx: this.transaction.txArray });
};
/*
* Post JSON data representing graph elements to the appropriate
* Rexster endpoint via http.
*
* @param {String} urlPath
* @param {Object} keys/values representing a graph element
* @param {Object} optional 'Content-Type' headers
* @return {Promise}
* @api private
*/
TransactionCommitter.prototype.postData = function(urlPath, data, headers) {
var self = this;
var deferred = q.defer();
var url = 'http://' + this.transaction.OPTS.host + ':' + this.transaction.OPTS.port + '/graphs/' + this.transaction.OPTS.graph + urlPath;
var options = {
url: url,
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json' // May be overridden
}
};
// Specify custom headers, if any
if(headers){
for(var prop in headers){
if(headers.hasOwnProperty(prop)){
options.headers[prop] = headers[prop];
}
}
}
request.post(options, function(err, res, body) {
if (err) {
// Handle any HTTP request error, not Rexster errors
console.error('Problem with request: ' + err);
deferred.reject(err);
}
body = JSON.parse(body);
if('success' in body && body.success === false){
//send error info with reject
if(self.transaction.pendingVertices && !!self.transaction.pendingVertices.length){
//This indicates that all new Vertices were created but failed to
//complete the rest of the tranasction so the new Vertices need deleted
self.rollbackVertices()
.then(function(result){
deferred.reject(result);
},function(error){
deferred.reject(error);
});
} else {
deferred.reject(body);
}
} else {
//This occurs after pendingVertices have been created
//and passed in to postData
if(!('results' in body) && self.transaction.pendingVertices && !!self.transaction.pendingVertices.length){
body.pendingVertices = [];
// push.apply(body.pendingVertices, self.transaction.pendingVertices);
body.pendingVertices.push(self.transaction.pendingVertices);
self.transaction.pendingVertices.length = 0;
}
if('tx' in data){
data.tx.length = 0;
}
deferred.resolve(body);
}
});
return deferred.promise;
};
/*
* A convenient method for posting in batch.
*
* @api private
*/
TransactionCommitter.prototype.postBatch = function(data, headers) {
return this.postData('/tp/batch/tx', data, headers);
};
/*
* A convenient method for posting vertices.
*
* @api private
*/
TransactionCommitter.prototype.postVertices = function(data, headers) {
return this.postData('/vertices', data, headers);
};
/*
* For all pending "edges", replace _inV and _outV references to Vertex
* object by references to Vertex _id.
*
* @api private
*/
TransactionCommitter.prototype.updateEdges = function() {
var transactionElement;
for (var k = 0; k < this.transaction.txArray.length; k++) {
transactionElement = this.transaction.txArray[k];
if(transactionElement._type == 'edge' && transactionElement._action == 'create'){
// Replace references to Vertex object by references to Vertex _id.
// TODO: Try replacing the following two checks with getters for _inV and _outV in Edge prototype.
if (isObject(transactionElement._inV)) {
transactionElement._inV = transactionElement._inV._id;
}
if (isObject(transactionElement._outV)) {
transactionElement._outV = transactionElement._outV._id;
}
}
}
};
/*
* Update the _id of vertices pending for creations with ids generated by
* the database.
*
* @param {Array} of element result fetched from the database
* @api private
*/
TransactionCommitter.prototype.updatePendingVertices = function(result) {
var inError = false;
//Update the _id for the created Vertices
for (var j = 0; j < result.length; j++) {
if('results' in result[j] && '_id' in result[j].results){
this.transaction.pendingVertices[j]._id = result[j].results._id;
} else {
this.inError = true;
}
}
};
/*
* For each pending vertices, build and return a "promise for all promise
* of creation of each vertex in the database".
*
* @return {Promise}
* @api private
*/
TransactionCommitter.prototype.getPostVerticesPromises = function() {
var promises = [],
types,
header = {'Content-Type':'application/vnd.rexster-typed-v1+json'};
for (var i = 0; i < this.transaction.pendingVertices.length; i++) {
types = addTypes(this.transaction.pendingVertices[i], this.transaction.typeMap);
promises.push(this.postVertices(types, header));
}
return q.all(promises);
};
/*
* Called when rolling back vertices: for each pending vertices, add a
* "removeVertex()" instruction to the transaction.
*
* @api private
*/
TransactionCommitter.prototype.removeVertices = function() {
for (var i = this.transaction.pendingVertices.length - 1; i >= 0; i--) {
//check if any vertices were created and create a Transaction
//to delete them from the database
if('_id' in this.transaction.pendingVertices[i]){
this.transaction.removeVertex(this.transaction.pendingVertices[i]._id);
}
}
this.transaction.pendingVertices.length = 0;
};
/*
* Remove all pending vertices in batch from the graph database. Internally
* calls "postBatch()".
*
* @return {Object} An error object
* @api private
*/
TransactionCommitter.prototype.rollbackVertices = function() {
var self = this;
var errObj = {
success: false,
message : ""
};
//In Error because couldn't create new Vertices. Therefore,
//roll back all other transactions
console.error('Problem with Transaction. Rolling back vertices...');
this.transaction.txArray.length = 0; // "clears" array
this.removeVertices();
//This indicates that nothing was able to be created as there
//is no need to create a tranasction to delete the any vertices as there
//were no new vertices successfully created as part of this Transaction
if (!this.transaction.txArray.length){
return q.fcall(function () {
errObj.message = "Could not complete transaction. Transaction has been rolled back.";
return errObj;
});
}
//There were some vertices created which now need to be deleted from
//the database. On success throw error to indicate transaction was
//unsuccessful. On fail throw error to indicate that transaction was
//unsuccessful and that the new vertices created were unable to be removed
//from the database and need to be handled manually.
return this.postBatch({ tx: this.transaction.txArray })
.then(function(success){
errObj.message = "Could not complete transaction. Transaction has been rolled back.";
return errObj;
}, function(fail){
errObj.message = "Could not complete transaction. Unable to roll back newly created vertices.";
errObj.ids = self.transaction.txArray.map(function(item){
return item._id;
});
self.transaction.txArray.length = 0;
return errObj;
});
};
return TransactionCommitter;
})();