define(['attribute', 'attributeList', 'retrievalResult', 'parameter', 'parameterList'],
function(Attribute, AttributeList, RetrievalResult, Parameter, ParameterList){
return (function() {
/**
* Initializes the database and all return values.
*
* @classdesc Storage handles the access to the database.
* @param {String} name
* @param {Number} time
* @param {Number} counter
* @returns {Storage}
* @constructs Storage
*/
function Storage(name, time, counter) {
/**
* Names of all stored Attributes (tableNames as string).
*
* @type {Array}
* @private
*/
this._attributeNames = [];
/**
* Data of a retrieval.
*
* @type {RetrievalResult}
* @private
*/
this._attributes = new RetrievalResult();
/**
* Cache before storing the new data in the database.
*
* @type {AttributeList}
* @private
*/
this._data = new AttributeList();
/**
* Names of all stored Attributes.
*
* @type {Number}
* @private
*/
this._dataCount = 0;
/**
* Time of the last flush.
*
* @type {Date}
* @private
*/
this._lastFlush = new Date();
/**
* Condition (ms) at which point of time data are supposed to be flushed.
* If the value is more than the value of 'timeCondition' ago, data should be
* flushed again. The initial value is two hours.
*
* @type {Number}
* @private
*/
this._timeCondition = 7200000;
/**
* Condition at which point of time data are supposed to be flushed.
* If at least 'countCondition' attributes are collected data will be flushed.
* Initial value is 5.
*
* @type {Number}
* @private
*/
this._countCondition = 5;
/**
* Associated database.
*
* @type {Database}
* @private
*/
this._db = '';
this._initStorage(name);
if(time && time === parseInt(time) && time != 0) this._timeCondition = time;
if(counter && counter === parseInt(counter) && counter != 0) this._countCondition = counter;
return this;
}
/**
* Returns the last retrieved Attributes.
*
* @returns {RetrievalResult}
*/
Storage.prototype.getCurrentData = function() {
return this._attributes;
};
/**
* Returns the names of all stored Attributes (tableNames as string).
*
* @returns {Array}
*/
Storage.prototype.getAttributesOverview = function() {
return this._attributeNames;
};
/**
* Initializes a new database.
*
* @private
* @param {String} name Name of the database.
*/
Storage.prototype._initStorage = function(name){
if(!window.openDatabase) {
console.log('Databases are not supported in this browser.');
}else{
this._db = window.openDatabase(name, "1.0", "DB_" + name, 1024*1024);
console.log('initStorage: ' + name);
}
};
/**
* Creates a new table. A table contains the values of one AttributeType.
* So the name is the AttributeName.
*
* @private
* @param {Attribute} attribute tableName (should be the attributeName)
* @param {?function} callback For alternative actions, if an asynchronous function is used.
*/
Storage.prototype._createTable = function(attribute, callback){
if(this._db){
var tableName = this._tableName(attribute);
var statement = 'CREATE TABLE IF NOT EXISTS "' + tableName + '" (value_, type_, created_)';
console.log('CREATE TABLE IF NOT EXISTS "' + tableName + '"');
if(callback && typeof(callback) == 'function'){
this._db.transaction(function(tx){tx.executeSql(statement);}, this._errorCB, callback);
} else {
this._db.transaction(function(tx){tx.executeSql(statement);}, this._errorCB, this._successCB);
}
if(!this._attributeNames.indexOf(attribute.getName()) > -1){
this._attributeNames.push(tableName);
}
}
};
/**
* Inserts value into a table. The name of the given Attribute
* identifies the table.
*
* @private
* @param {Attribute} attribute Attribute that should be stored.
* @param {?function} callback For alternative actions, if an asynchronous function is used.
*/
Storage.prototype._insertIntoTable = function(attribute, callback){
if(this._db && attribute && attribute.constructor === Attribute){
var tableName = this._tableName(attribute);
var statement = 'INSERT INTO "' + tableName
+ '" (value_, type_, created_) VALUES ("'
+ attribute.getValue() + '", "'
+ attribute.getType() + '", "'
+ attribute.getTimestamp() + '")';
console.log('INSERT INTO "'+tableName+'" VALUES ('+attribute.getValue()+", "+attribute.getType()+", "+attribute.getTimestamp());
if(callback && typeof(callback) == 'function'){
this._db.transaction(function(tx){tx.executeSql(statement);}, this._errorCB, callback);
} else {
this._db.transaction(function(tx){tx.executeSql(statement);}, this._errorCB, this._successCB);
}
}
};
/**
* error function
*
* @callback
* @private
*/
Storage.prototype._errorCB = function(err) {
console.log("Error processing SQL: "+err.message);
};
/**
* success function
*
* @callback
* @private
*/
Storage.prototype._successCB = function() {
console.log("SQL processed successfully!");
};
/**
* Sets the attributeNames array.
*
* @param {?function} [callback] For alternative actions, if an asynchronous function is used.
*/
Storage.prototype.getAttributeNames = function(callback){
if(this._db){
var self = this;
this._db.transaction(function(tx) {
self._queryTables(tx, self, callback);
}, function(error) {
self._errorCB(error);
}
);
}
};
/**
* Sets the attributeNames array. Is used in getAttributeNames().
*
* @callback
* @private
* @param {*} tx
* @param {Storage} self
* @param {?function} callback For alternative actions, if an asynchronous function is used.
*/
Storage.prototype._queryTables = function(tx, self, callback){
var statement = "SELECT * from sqlite_master WHERE type = 'table'";
tx.executeSql(statement, [], function(tx,results) {
self._queryTableSuccess(tx, results, self, callback);
}, function(error) {
self._errorCB(error);
});
};
/**
* Success function for queryTable.
*
* @callback
* @private
* @param {*} tx
* @param {*} results
* @param {Storage} self
* @param {?function} callback
*/
Storage.prototype._queryTableSuccess = function(tx, results, self, callback){
self._attributeNames = [];
var len = results.rows.length;
for(var i=0; i<len; i++){
var table = results.rows.item(i).name;
if(table.indexOf("DatabaseInfoTable") == -1){
self._attributeNames.push(results.rows.item(i).name);
}
}
if(callback && typeof(callback) == 'function'){
callback();
}
};
/**
* Verifies if a table for an attribute exists.
*
* @private
* @param {(Attribute|String)} attributeOrName Attribute or name for the verification.
* @returns {boolean}
*/
Storage.prototype._tableExists = function(attributeOrName){
if(attributeOrName.constructor === Attribute){
var name = this._tableName(attributeOrName);
return this._attributeNames.indexOf(name) > -1;
} else if(typeof attributeOrName === 'string'){
return this._attributeNames.indexOf(attributeOrName) > -1;
}
return false;
};
/**
* Retrieves a table and sets the RetrievalResult.
*
* @param {String} tableName Name for the table that should be retrieved.
* @param {?function} callback For additional actions, if an asynchronous function is used.
*/
Storage.prototype.retrieveAttributes = function(tableName, callback){
console.log("retrieveAttributes from "+tableName);
if(this._db){
var self = this;
self._flushStorage();
this._db.transaction(function(tx) {
self._queryValues(tx, tableName, self, callback);
}, function(error) {
self._errorCB(error);
});
}
};
/**
* Query function for given attribute.
*
* @callback
* @private
* @param {*} tx
* @param {String} tableName Name for the table that should be retrieved.
* @param {Storage} self
* @param {?function} callback For additional actions, if an asynchronous function is used.
*/
Storage.prototype._queryValues = function(tx, tableName, self, callback){
if(self._tableExists(tableName)){
console.log('SELECT * FROM "' +tableName+"'");
var statement = 'SELECT * FROM "' + tableName+'"';
tx.executeSql(statement, [],
function(tx, results) {
self._queryValuesSuccess(tx, results, tableName, self, callback);
}, function(error) {
self._errorCB(error);
});
} else {
console.log('Table "'+tableName+'" unavailable');
}
};
/**
* Success function for retrieveAttributes().
* Puts the retrieved data in RetrievalResult object.
*
* @callback
* @private
* @param {*} tx
* @param {*} results
* @param {String} tableName Name of the searched attribute.
* @param self
* @param {?function} callback For additional actions, if an asynchronous function is used.
*/
Storage.prototype._queryValuesSuccess = function(tx, results, tableName, self, callback){
var len = results.rows.length;
var attributeList = [];
var attributeName = this._resolveAttributeName(tableName);
var parameterList = this._resolveParameters(tableName);
for(var i=0; i<len; i++){
var attribute = new Attribute().
withName(attributeName).withValue(results.rows.item(i).value_).
withType(results.rows.item(i).type_).
withTimestamp(results.rows.item(i).created_).
withParameters(parameterList);
attributeList.push(attribute);
}
self._attributes = new RetrievalResult().withName(tableName)
.withTimestamp(new Date())
.withValues(attributeList);
if(callback && typeof(callback) == 'function'){
callback();
}
};
/**
* Stores the given Attribute.
* If the flush condition does not match,
* the data is first added to the local cache before.
*
* @public
* @param {Attribute} attributeValue Value that should be stored.
*/
Storage.prototype.store = function(attributeValue) {
this._addData(attributeValue);
if(this._checkFlushCondition){
this._flushStorage();
this._resetForFlush();
}
};
/**
* Adds data to the local cache.
* The cache is used to decrease the database access.
*
* @private
* @param {Attribute} _attribute Value that should be stored.
*/
Storage.prototype._addData = function(_attribute){
if(_attribute.constructor === Attribute){
this._data.put(_attribute);
this._dataCount++;
}
};
/**
* Verifies the flush conditions.
*
* @private
* @returns {boolean}
*/
Storage.prototype._checkFlushCondition = function(){
if(this._dataCount > this._countCondition){
return true;
}
var currentDate = new Date();
if((currentDate.getTime() - this._lastFlush.getTime()) < this._timeCondition ){
return true;
} //2 stunden??
return false;
};
/**
* Clears the local cache.
*
* @private
*/
Storage.prototype._resetForFlush = function(){
this._data = new AttributeList();
this._dataCount = 0;
this._lastFlush = new Date();
};
/**
* Stores all data from the local cache to the database.
*
* @private
*/
Storage.prototype._flushStorage = function(){
var self = this;
if(self._data.size() == 0){
return;
}
for(var i in self._data.getItems()){
var item = self._data.getItems()[i];
if(!self._tableExists(item)){
self._createTable(item, function() {
self._insertIntoTable(item);
});
} else {
self._insertIntoTable(item);
}
}
};
/**
* Sets the time condition for flush.
*
* @param {Number} time time in ms
*/
Storage.prototype.setTimeCondition = function(time){
this._timeCondition = time;
};
/**
* Sets the counter for flush.
*
* @param {Number} _counter counter
*/
Storage.prototype.setCountCondition = function(_counter){
this._countCondition = _counter;
};
/**
* Returns the current time condition for flush.
*
* @returns {Number}
*/
Storage.prototype.getTimeCondition = function(){
return this._timeCondition;
};
/**
* Returns the current count condition for flush.
*
* @returns {Number}
*/
Storage.prototype.getCountCondition = function(){
return this._countCondition;
};
/****************************
* Helper *
****************************/
/**
* Builds the tableName for the given attribute.
*
* @private
* @param {Attribute} attribute Attribute that should be stored.
* @returns{String}
*/
Storage.prototype._tableName = function(attribute){
return attribute.toString(true);
};
/**
* Extracts the attributeName form the table name.
*
* @private
* @param {String} tableName Table name that should be resolved.
* @returns{String}
*/
Storage.prototype._resolveAttributeName = function(tableName){
var resolvedTableName = tableName.split('__');
return resolvedTableName[0];
};
/** Extracts the parameters form the table name.
*
* @private
* @param {String} _tableName Table name that should be resolved.
* @returns{String}
*/
Storage.prototype._resolveParameters = function(_tableName){
var resolvedTableName = _tableName.split('__');
var parameterList = new ParameterList();
for(var i = 1; i < resolvedTableName.length; i++ ){
var resolvedParameter = resolvedTableName[i].split('_');
var parameter= new Parameter().withKey(resolvedParameter[0]).withValue(resolvedParameter[1]);
parameterList.put(parameter);
}
return parameterList;
};
return Storage;
})();
}
);