(function(window) {
// retry infinite is hard coded atm
var Gitana = window.Gitana;
var OBJECTS_PER_REQUEST = 100;
var STATUS_POLL_INTERVAL = 2 * 1000; // 2 seconds
var TRANSACTION_STATUS_FINISHED = 'FINISHED';
var chunk = function(array, size) {
var chunks = [];
for (var i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
};
/**
* Given a transaction add all of the tasks and then commit.
*/
var commit = function(transaction) {
var allObjects = transaction.objects;
var requests = [];
var q = new Gitana.Queue();
// split up into chunks of objects
var chunks = chunk(allObjects, OBJECTS_PER_REQUEST);
for (var i = 0; i < chunks.length; i++) {
var objects = chunks[i];
q.add(function(index, objects, transaction) {
return function() {
var def = new Gitana.Defer();
//console.log("CHUNK " + index + ", size: " + objects.length);
// TRANSACTION_TEST
if (Gitana.Transaction.testMode)
{
console.log("POST /transactions/" + transaction.getId() + "/add");
def.resolve(objects);
}
else
{
var payload = {
"objects": objects
};
transaction.getDriver().gitanaPost('/transactions/' + transaction.getId() + '/add', {}, payload, function(res) {
def.resolve(objects);
}, function(err) {
// when things fail, we don't retry, to fail the entire transaction before committing
def.reject(err);
});
}
return def.promise;
};
}(i, objects, transaction));
}
var def2 = new Gitana.Defer();
q.go().then(function(reses) {
// TRANSACTION_TEST
if (Gitana.Transaction.testMode)
{
console.log("POST /transaction/" + transaction.getId() + "/commit");
def2.resolve();
}
else
{
transaction.getDriver().gitanaPost('/transactions/' + transaction.getId() + '/commit', {}, {}, function(res) {
def2.resolve(res);
}, function(err) {
def2.reject(err);
});
}
}, def2.reject);
return def2.promise;
};
/**
* Tell the server to cancel this transaction
*/
var cancel = function(transaction) {
var def = new Gitana.Defer();
// TRANSACTION_TEST
if (Gitana.Transaction.testMode)
{
console.log("DELETE /transactions/" + transaction.getId());
def.resolve();
}
else
{
transaction.getDriver().gitanaDelete('/transactions/' + transaction.getId(), {}, function(res) {
def.resolve(res);
}, function(err) {
def.reject(err);
});
}
return def.promise;
};
/**
* Add an object to a transaction
*/
var addObject = function(transaction, object) {
if (object.data && Gitana.isString(object.data)) {
object.data = {
"_doc": object.data
};
}
transaction.objects.push(object);
};
/**
* Transaction constructor
*
* Options doesn't really do anything ATM
*
* transaction.promise is a promise that gets resolved/rejected once the http
* request completes which creates the transaction on the server side.
*/
var Transaction = function(container, options) {
// object queue
this.objects = [];
this.getContainer = function() {
return container;
};
if (container) {
this['for'](container);
}
};
Transaction.prototype['for'] = function(container) {
if (this.promise) {
throw new Error('Container for transaction has already been set');
}
var self = this;
var def = new Gitana.Defer();
this.promise = def.promise;
this.getDriver().gitanaPost(this.getUri(), {}, {}, function(res) {
self.getId = function() { return res._doc; };
self.getContainerReference = function() { return res['container-reference']; };
def.resolve(self);
}, function(err) {
def.reject(err);
});
};
/**
* Cloud CMS
*/
/**
* Return the driver instance of this transaction's container
*/
Transaction.prototype.getDriver = function() {
return this.getContainer().getDriver();
};
/**
* Returns the uri used to create this transaction
*/
Transaction.prototype.getUri = function() {
return '/transactions?reference=' + this.getContainer().ref();
};
/**
* Transaction API
*/
/**
* Add a write action to the transaction
*/
Transaction.prototype.write = function(data) {
if (typeof this.promise === 'undefined') {
throw new Error('You must set the transaction\'s container with the "for" method before calling this method' );
}
this.promise.then(function(self) {
if (Gitana.isArray(data)) {
for (var i = 0; i < data.length; i++) {
var d = data[i];
addObject(self, {
header: {
type: 'node',
operation: 'write'
},
data: d
});
}
} else {
addObject(self, {
header: {
type: 'node',
operation: 'write'
},
data: data
});
}
});
return this;
};
Transaction.prototype.create = Transaction.prototype.update = Transaction.prototype.write;
/**
* Add a delete action to the transaction
*/
Transaction.prototype.del = function(data) {
if (typeof this.promise === 'undefined') {
throw new Error('You must set the transaction\'s container with the "for" method before calling this method' );
}
if (typeof(data) === "string") {
data = {
"_doc": data
};
}
this.promise.then(function(self) {
if (Gitana.isArray(data)) {
for (var i = 0; i < data.length; i++) {
var d = data[i];
addObject(self, {
header: {
type: 'node',
operation: 'delete'
},
data: d
});
}
} else {
addObject(self, {
header: {
type: 'node',
operation: 'delete'
},
data: data
});
}
});
return this;
};
/**
* Commit this transaction
*/
Transaction.prototype.commit = function() {
var def = new Gitana.Defer();
var self = this;
if (typeof this.promise === 'undefined') {
throw new Error('You must set the transaction\'s container with the "for" method before calling this method' );
}
this.promise.then(function(self) {
commit(self).then(function() {
(function pollLoop() {
// TRANSACTION_TEST
if (Transaction.testMode)
{
console.log("GET /transactions/" + self.getId() + "/status");
def.resolve();
}
else
{
self.getDriver().gitanaGet('/transactions/' + self.getId() + '/status', {}, {}, function(res) {
if (res.status === TRANSACTION_STATUS_FINISHED) {
def.resolve(res.results);
} else {
setTimeout(pollLoop, STATUS_POLL_INTERVAL);
}
}, function(err) {
def.reject(err);
});
}
})();
}, def.reject);
});
return def.promise;
};
/**
* Cancel this transaction
*/
Transaction.prototype.cancel = function() {
var def = new Gitana.Defer();
if (typeof this.promise === 'undefined') {
throw new Error('You must set the transaction\'s container with the "for" method before calling this method' );
}
this.promise.then(function(self) {
cancel(self).then(def.resolve, def.reject);
});
return def.promise;
};
/**
* Exports
*/
Gitana.Transaction = Transaction;
Gitana.TypedIDConstants.TYPE_TRANSACTION = 'Transaction';
Gitana.ObjectFactory.prototype.transaction = function(container, object) {
return this.create(Gitana.Transaction, container, object);
};
var createTransaction = function(container) {
return new Transaction(container, {
});
};
Gitana.transactions = function() {
var r = {};
r.create = function(container) {
return container ? createTransaction(container) : {
"for": createTransaction
};
};
return r;
};
})(window);