/**
* This module representing a Storage.
* The Storage handles the access to the database.
*
* @module Widget
* @fileOverview
*/
define(['easejs', 'attributeValue', 'attributeValueList', 'attributeType',
'retrievalResult', 'parameter', 'parameterList'],
function( easejs, AttributeValue, AttributeValueList, AttributeType,
RetrievalResult, Parameter, ParameterList){
var Class = easejs.Class;
var Storage = Class('Storage',
{
/**
* @alias attributeNames
* @private
* @type {Array}
* @memberof Storage#
* @desc Names of all stored Attributes (tableNames as string).
*/
'private attributeNames' : [],
/**
* @alias attributes
* @private
* @type {RetrievalResult}
* @memberof Storage#
* @desc Data of a retrieval.
*/
'private attributes' : '',
/**
* @alias data
* @private
* @type {AttributeValueList}
* @memberof Storage#
* @desc Cache before storing the new data in the database.
*/
'private data' : [],
/**
* @alias dataCount
* @private
* @type {Integer}
* @memberof Storage#
* @desc Names of all stored Attributes.
*/
'private dataCount' : '',
/**
* @alias lastFlush
* @private
* @type {Date}
* @memberof Storage#
* @desc Time of the last flush.
*/
'private lastFlush' : '',
/**
* @alias timeCondition
* @private
* @type {Integer}
* @memberof Storage#
* @desc 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.
*/
'private timeCondition' : 7200000,
/**
* @alias countCondition
* @private
* @type {Number}
* @memberof Storage#
* @desc 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.
*/
'private countCondition' : 5,
/**
* @alias db
* @private
* @type {Database}
* @memberof Storage#
* @desc Associated database.
*/
'private db' : '',
/**
* Constructor: Initializes the database and all return values.
*
* @class Storage
* @classdesc Storage handles the access to the database.
* @requires easejs
* @requires AttributeValue
* @requires AttributeValueList
* @requires Parameter
* @requires ParameterList
* @requires RetrievalResult
* @constructs Storage
*/
'public __construct' : function(_name, _time, _counter){
this.initStorage(_name);
this.attributes = new RetrievalResult();
this.data = new AttributeValueList();
this.dataCount = 0;
this.lastFlush = new Date();
if(_time && _time === parseInt(_time) && _time!=0)
this.timeCondition = _time;
if(_counter && _counter === parseInt(_counter) && _counter != 0)
this.countCondition = _counter;
},
/**
* Returns the last retrieved Attributes.
*
* @public
* @alias getCurrentData
* @memberof Storage#
* @returns {RetrievalResult}
*/
'public getCurrentData' : function(){
return this.attributes;
},
/**
* Returns the names of all stored Attributes (tableNames as string).
*
* @public
* @alias getAttributesOverview
* @memberof Storage#
* @returns {Array}
*/
'public getAttributesOverview' : function(){
return this.attributeNames;
},
/**
* Initializes a new database.
*
* @private
* @alias initStorage
* @memberof Storage#
* @param {String} _name Name of the database.
*/
'private 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
* @alias createTable
* @memberof Storage#
* @param {String} _attribute tableName (should be the attributeName)
* @param {?function} _function For alternative actions, if an asynchronous function is used.
*/
'private createTable' : function(_attribute, _function){
if(this.db){
var tableName = this.tableName(_attribute);
var statement = 'CREATE TABLE IF NOT EXISTS ' + tableName + ' (value_, type_, created_)';
if(_function && typeof(_function) == 'function'){
this.db.transaction(function(tx){tx.executeSql(statement);}, this.errorCB, _function);
} else {
this.db.transaction(function(tx){tx.executeSql(statement);}, this.errorCB, this.successCB);
}
if(!this.attributeNames.indexOf(name) > -1){
this.attributeNames.push(tableName);
}
console.log('CREATE TABLE IF NOT EXISTS ' + tableName);
}
},
/**
* Inserts value into a table. The name of the given Attribute
* identifies the table.
*
* @private
* @alias insertIntoTable
* @memberof Storage#
* @param {AttributeValue} _attributeValue Attribute that should be stored.
* @param {?function} _function For alternative actions, if an asynchronous function is used.
*/
'private insertIntoTable' : function(_attributeValue, _function){
if(this.db && _attributeValue && Class.isA(AttributeValue, _attributeValue)){
var tableName = this.tableName(_attributeValue);
var statement = 'INSERT INTO ' + tableName
+ ' (value_, type_, created_) VALUES ("'
+ _attributeValue.getValue() + '", "'
+ _attributeValue.getType() + '", "'
+ _attributeValue.getTimestamp() + '")';
if(_function && typeof(_function) == 'function'){
this.db.transaction(function(tx){tx.executeSql(statement);}, this.errorCB, _function);
} else {
this.db.transaction(function(tx){tx.executeSql(statement);}, this.errorCB, this.successCB);
}
console.log('INSERT INTO '+tableName+' VALUES ('+_attributeValue.getValue()+", "+_attributeValue.getType()+", "+_attributeValue.getTimestamp());
}
},
/**
* error function
*
* @callback
* @private
* @alias errorCB
* @memberof Storage#
*/
'private errorCB' : function(err) {
console.log("Error processing SQL: "+err.message);
},
/**
* success function
*
* @callback
* @private
* @alias successCB
* @memberof Storage#
*/
'private successCB' : function() {
console.log("SQL processed successfully!");
},
/**
* Sets the attributeNames array.
*
* @public
* @alias getAttributeNames
* @memberof Storage#
* @param {?function} _function For alternative actions, if an asynchronous function is used.
*/
'public getAttributeNames' : function(_function){
if(this.db){
var self = this;
this.db.transaction(function(_tx){self.queryTables(_tx,self, _function);},
function(error){self.errorCB(error);} );
}
},
/**
* Sets the attributeNames array. Is used in getAttributeNames().
*
* @callback
* @private
* @alias queryTables
* @memberof Storage#
* @param {*} _tx
* @param {@this} self
* @param {?function} _function For alternative actions, if an asynchronous function is used.
*/
'private queryTables' : function(_tx, self, _function){
var statement = "SELECT * from sqlite_master WHERE type = 'table'";
_tx.executeSql(statement, [], function(_tx,results){self.queryTableSuccess(_tx,results,self, _function);},
function(error){self.errorCB(error);});
},
/**
* Success function for queryTable.
*
* @callback
* @private
* @alias queryTableSucces
* @memberof Storage#
* @param {*} _tx
* @param {*} results
* @param {@this} self
*/
'private queryTableSuccess' : function(_tx, results, self, _function){
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(_function && typeof(_function) == 'function'){
_function();
}
},
/**
* Verifies if a table for an attribute exists.
*
* @private
* @alias tableExists
* @memberof Storage#
* @param {(AttributeValue|String)} _attribute Attribute or name for the verification.
* @returns {boolean}
*/
'private tableExists' : function(_attribute){
if(Class.isA(AttributeValue, _attribute) || Class.isA(AttributeType, _attribute)){
var name = this.tableName(_attribute);
return this.attributeNames.indexOf(name) > -1;
} else if(typeof _attribute === 'string'){
return this.attributeNames.indexOf(_attribute) > -1;
}
return false;
},
/**
* Retrieves a table and sets the RetrievalResult.
*
* @public
* @alias retrieveAttributes
* @memberof Storage#
* @param {String} _tableName Name for the table that should be retrieved.
* @param {?function} _function For additional actions, if an asynchronous function is used.
*/
'public retrieveAttributes' : function(_tableName, _function){
if(this.db){
var self = this;
self.flushStorage();
this.db.transaction(function(_tx){self.queryValues(_tx,_tableName,self, _function);},
function(error){self.errorCB(error);} );
}
},
/**
* Query function for given attribute.
*
* @callback
* @private
* @alias queryValues
* @memberof Storage#
* @param {*} _tx
* @param {String} _tableName Name for the table that should be retrieved.
* @param {@this} self
* @param {?function} _function For additional actions, if an asynchronous function is used.
*/
'private queryValues' : function(_tx,_tableName,self, _function){
if(self.tableExists(_tableName)){
var statement = 'SELECT * FROM ' + _tableName;
_tx.executeSql(statement, [],
function(_tx,results){self.queryValuesSuccess(_tx,results,_tableName, self, _function);},
function(error){self.errorCB(error);});
} else {
console.log('Table "'+_tableName+'" unavailable');
}
},
/**
* Success function for retrieveAttributes().
* Puts the retrieved data in RetrievalResult object.
*
* @callback
* @private
* @alias queryValuesSucces
* @memberof Storage#
* @param {*} _tx
* @param {*} results
* @param {String} _tableName Name of the searched attribute.
* @param self
* @param {?function} _function For additional actions, if an asynchronous function is used.
*/
'private queryValuesSuccess' : function(_tx, results,_tableName, self, _function){
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 AttributeValue().
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(_function && typeof(_function) == 'function'){
_function();
}
},
/**
* Stores the given Attribute.
* If the flush condition does not match,
* the data is first added to the local cache before.
*
* @public
* @alias store
* @memberof Storage#
* @param {AttributeValue} _attributeValue Value that should be stored.
*/
'public 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
* @alias addData
* @memberof Storage#
* @param {AttributeValue} _attributeValue Value that should be stored.
*/
'private addData' : function(_attributeValue){
if(Class.isA(AttributeValue, _attributeValue)){
this.data.put(_attributeValue);
this.dataCount++;
}
},
/**
* Verifies the flush conditions.
*
* @private
* @alias checkFlushCondition
* @memberof Storage#
* @returns {boolean}
*/
'private checkFlushCondition' : function(){
if(this.dataCount > this.countCondition){
return true;
}
var currentDate = new Date();
if((currentDate.getTime() - lastFlush.getTime()) < this.timeCondition ){
return true;
} //2 stunden??
return false;
},
/**
* Clears the local cache.
*
* @private
* @alias resetForFlush
* @memberof Storage#
*/
'private resetForFlush' : function(){
this.data = new AttributeValueList();
this.dataCount = 0;
this.lastFlush = new Date();
},
/**
* Stores all data from the local cache to the database.
*
* @private
* @alias flushStorage
* @memberof Storage#
*/
'private flushStorage' : function(){
var self = this;
if(self.data.size() == 0){
return;
}
var keys = self.data.getKeys();
for(var i in keys){
var key = keys[i];
var item = self.data.getItem(key);
if(!self.tableExists(item)){
self.createTable(item, function(){self.insertIntoTable(item);});
} else {
self.insertIntoTable(item);
}
}
},
/**
* Sets the time condition for flush.
*
* @public
* @alias setTimeCondition
* @memberof Storage#
* @param {integer} _time time in ms
*/
'public setTimeCondition' : function(_time){
this.timeCondition = _time;
},
/**
* Sets the counter for flush.
*
* @public
* @alias setCountCondition
* @memberof Storage#
* @param {integer} _counter counter
*/
'public setCountCondition' : function(_counter){
this.countCondition = _counter;
},
/**
* Returns the current time condition for flush.
*
* @public
* @alias getTimeCondition
* @memberof Storage#
* @returns {integer}
*/
'public getTimeCondition' : function(){
return this.timeCondition;
},
/**
* Returns the current count condition for flush.
*
* @public
* @alias getCountCondition
* @memberof Storage#
* @returns{integer}
*/
'public getCountCondition' : function(){
return this.countCondition;
},
/****************************
* Helper *
****************************/
/**
* Builds the tableName for the given attribute.
*
* @private
* @alias tableName
* @memberof Storage#
* @param {AttributeValue} _attribute Attribute that should be stored.
* @returns{String}
*/
'private tableName' : function(_attribute){
var tableName = _attribute.getName();
var parameterList = _attribute.getParameters();
if(!parameterList.isEmpty()){
var keys = parameterList.getKeys();
for(var i in keys){
tableName = tableName + '__' +keys[i] + '_'+parameterList.getItem(keys[i]);
}
}
return tableName;
},
/**
* Extracts the attributeName form the table name.
*
* @private
* @alias resolveAttributeName
* @memberof Storage#
* @param {String} _tableName Table name that should be resolved.
* @returns{String}
*/
'private resolveAttributeName' : function(_tableName){
var resolvedTableName = _tableName.split('__');
return resolvedTableName[0];
},
/** Extracts the parameters form the table name.
*
* @private
* @alias resolveParameters
* @memberof Storage#
* @param {String} _tableName Table name that should be resolved.
* @returns{String}
*/
'private 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;
});