/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) 2004-2010 Open-Xchange, Inc.
 * Mail: info@open-xchange.com 
 * 
 * @author Jan Finsel <jan.finsel@open-xchange.com>
 * 
 */




  /////////////////////////////////////
 //   Key and Small Key             //
/////////////////////////////////////
/***
 * Creates a new Key
 * @class Key
 * The class key is needed for MRUKeyList, every key has two functions.
 * hashCode and equals   
 */
function Key() {
}
Key.prototype.equals = function(keyObject1) {
	return this.module==keyObject1.module &&
	    this.folder_id == keyObject1.folder_id &&
	    this.id == keyObject1.id &&
	    (this.recurrence_position || 0) ==
	        (keyObject1.recurrence_position || 0);
}
Key.prototype.hashCode = function() {
    if(this.calculatedHash) { return this.calculatedHash; }
	if(this.recurrence_position || (this.recurrence_position == 0)) {
		if(!isNaN(this.folder_id) && !isNaN(this.id) && !isNaN(this.recurrence_position)) {
		  this.calculatedHash = parseInt(this.folder_id) + parseInt(this.id) +
		      Number(this.recurrence_position);
          return this.calculatedHash;		 	
		} else {
		  this.calculatedHash=(this.folder_id+this.id+this.recurrence_position);
          return this.calculatedHash;	
		}
	} else {
		if(!isNaN(this.folder_id) && !isNaN(this.id)) {
			this.calculatedHash= (parseInt(this.folder_id)+parseInt(this.id));
			return this.calculatedHash;
	    } else {
		    this.calculatedHash=(this.folder_id+this.id);
            return this.calculatedHash;
		}
	}
}
Key.createfromObject= function(keyObject) {
	var element=new Key();
	element.module=keyObject.module;
	element.folder_id=keyObject.folder_id;
	element.id=keyObject.id;
	element.recurrence_position=keyObject.recurrence_position;
	return element;
}

  /////////////////////////////////////
 //   List-Recently-Used Key list   //
/////////////////////////////////////
/**
 * Creates an LRU list.
 * @class List-recently-used list (LRU list).
 * An MRU list limits the number of stored items by discarding
 * the least-recently referenced item, when storing a new item would otherwise
 * exceed the limit.
 * @param {Int} size The maximum size of the list.
 */
function LRUKeyList(size) {
	this.free = size;
	if(!this.free) {
        this.free=-1;
    }
    this.cache = {};
    this.list = new LinkedList();
}

LRUKeyList.prototype = {
    /**
     * Retrieves an item from the list.loadconfig();
     * @param key The key of the item to retrieve.
     * @return The retrieved item or <code>undefined</code> if the item was not
     * found.
     */
    get: function(key,changeList,wholeobject) {
        var items = this.cache[key.hashCode()];
        var item;
        if(items) {
            for (var i=0;i<items.length;i++) {
                if(key.equals(items[i].key)) {
                    item=items[i];  
                    break;
                }
            }
        }
        if(!item) return undefined;
        if(changeList) {
            this.list.remove(item);
            this.list.addLast(item);
        }
        if(!wholeobject) return item.data;
        else { return item; }
    },
	
	/**
     * Stores an item in the list.
     * @param key The key under which to store the item.
     * @param value The item to store in the list.
     * @return A removed item if cache is full or <code>undefined</code>
     * found.
     */
    set: function(key, value) {
        var item = this.get(key,false,true);
        if (item)
            item.data = value;
        else {
            item = {key: key, data: value};
            this.list.addLast(item);
            var hash = key.hashCode();
            if(!this.cache[hash]) this.cache[hash]=new Array();
			this.cache[hash].push(item);
            if (this.free) {
                this.free--;
            } else {
                var key1=this.list.removeFirst().key;
                var ret=this.get(key1,false,true);
                this.remove(key1);
				this.free--;
                return ret;
            } 
        }
    },


    /**
     * Removes an item from the list.
     * @param key The key of the item to remove.
     */
    remove: function(key) {
        var items = this.cache[key.hashCode()];
        var item;
        if(items) {
            for (var i=0;i<items.length;i++) {
                if(key.equals(items[i].key)) {
					this.list.remove(items[i]);
                    items.splice(i,1);
					if(!items.length) {
						this.cache[key.hashCode()]= undefined;
					}
					this.free++;
					break;  
                }
            }
        }
    },
	/**
     * Returns an array of keys.
     * @returns an Array of keys in LRUList
     */
	keys : function() {
	   var myKeys=new Array();
	   for(var hashcode in this.cache) {
	   	   if(this.cache[hashcode]) {
	           for(var i=0;i<this.cache[hashcode].length;i++) {
		          if(this.cache[hashcode][i].key) { myKeys.push(this.cache[hashcode][i].key) }	
		       }
		   }
	   }
	   return myKeys;
	}
};
  /////////////////////////////////////
 //   OX Collection                 //
/////////////////////////////////////

function OXCollection() {
    this.last_modified; //Maximum of last modified
    this.lastRequest;   //Last request of 
    this.criteria;      //Criteria
    this.order;         //Order 
    this.objects=new Array();
    this.map_objects=new LRUKeyList();
}

OXCollection.prototype = {
	/**
	 * Returns the index of the given Key
	 * @param  {Object} key
	 * @return {int} the index or null if not founds
	 */
	getIndex: function(key) {
		return this.map_objects.get(key);
	}
}

  /////////////////////////////////////
 //   HELP FUNCTIONS                //
/////////////////////////////////////
function switchStringObject(object) {
    var myObject=new Object();
    for(var i in object) {
        myObject[object[i]]=i;
    }
    return myObject;
}

  /////////////////////////////////////
 //   OXCACHE                       //
/////////////////////////////////////
var OXCache = {}
/**
 * Time {int} (in Milliseconds), at which an existing object in Cache is expired and ought
 * ought to requested again.
 * @value 0 existing objects in Cache never expire
 */
OXCache.updateTime       = 300000; //Time in Milliseconds for next update if is requested
/**
* Object size {int}, number of objects, which are maximum in Cache.
*/
OXCache.objectSize       = 20000;
/**
* Object size {int}, number of criteria list, which are maximum in Cache.
*/
OXCache.criteriaSize     = 10;
/**
 * @private
 * Sequence (int) of Cache 
 */
OXCache.sequence    = 0;
/**
* @private
* Returns next Sequence .
* @return The next sequence value {int}
*/
OXCache.getSequence = function() {
    return OXCache.sequence++;
}
/**
* @private 
* Registered module mappings.
*/
OXCache.moduleMappings   = new Object();
/**
* @private
* Cache for Objects.
*/
OXCache.cachedObjects    = new LRUKeyList(OXCache.objectSize);
/**
* @private
* Cache for Collections.
*/
OXCache.cachedCollections = new MRUList(OXCache.criteriaSize); //new Array();
/**
* @private 
* Temporary Trash for objects, which are removed out of cachedCollections between the last requests.
*/
OXCache.cachedObjectsTrash = new LRUKeyList();
/**
* @private 
* Temporary Trash for collections, which are removed out of cachedCollections between the last requests.
*/
OXCache.cachedCollectionsTrash = new Array();
/**
* @private 
* Short key mapping for objects.
*/
OXCache.objectShortKey   = new LRUKeyList();
/**
* @private 
* Request, which will be requested after execute all callbacks.
*/
OXCache.waitingrequests = new Array();
/**
* @private
* Registered modified callbacks
*/
OXCache.modifiedCallbacks = new Object();
/**
* @private 
* Registered callbacks which are executed just one time.
*/
OXCache.actualCallbacks = new Object();
/**
* @private
* List of modified Objects, which are changed since last request.
*/
OXCache.modifiedObjects= new Array();
/**
* @private
* List of possible deleted Objects in list requests, which are changed since last request.
*/
OXCache.deletedObjects= new Array();
/**
 * @private
 * List all all requests send to server
 */
OXCache.requested = {
    CNOT: [], CEXP: [], CMIS: {}, LNOT: {}, LEXP: {}, LMIS: {}
};
/**
 * @private
 * Checks if OXCache is in execution 
 */
OXCache.executeActive=false;

/**
* @private
* Adds request to waiting requests. Waiting requests will be inquired after all callbacks are executed.
* @param module {string}, String of module
* @param collection {OXCollection}, Collection object
* @param cb {function}, Callback 
*/
OXCache.requestSequence = 0;
OXCache.getRequestSequence = function() {
    return OXCache.requestSequence++;
}

OXCache.addRequest= function(module,collection,cb,uniqueName) {
	OXCache.waitingrequests.push({module : module ,collection : collection , cb : cb, uniqueName : uniqueName });
}
/**
* @private
* Join of Cache. Executes all execute functions in Cache, if all started requests are returned 
* and clears tmp variables at last step.
*/
OXCache.join = new Join(function() {
	OXCache.callbackArray=new Object();
	OXCache.modifiedArray=new Object();
	for(var i in OXCache.actualCallbacks) {
        OXCache.callbackArray[i]=true;
    }
    for(var i in OXCache.modifiedCallbacks) {
        OXCache.modifiedArray[i]=true;
    }
    
	for (var i in OXCache.moduleMappings) {
        if(OXCache.moduleMappings[i].execute) {
            OXCache.moduleMappings[i].execute();
        }
    }
    for(var i in OXCache.callbackArray) {
        delete OXCache.actualCallbacks[i];
    }
    for(var i in OXCache.modifiedObjects) {
       OXCache.modifiedObjects[i].modified=false;
    }
    OXCache.modifiedObjects=new Array();
    OXCache.deletedObjects=new Array();
    for(var i in OXCache.cachedCollections.cache) {
       OXCache.cachedCollections.cache[i].data.modified=false;
    }
	
    var wr = {};
    for (var i = 0; i < OXCache.waitingrequests.length; i++)
        wr[OXCache.waitingrequests[i].uniqueName] = OXCache.waitingrequests[i];
    OXCache.waitingrequests=new Array();
	for(var i in wr) {
        OXCache.actualCallbacks[i]={
			module : wr[i].module , 
		    collection : wr[i].collection ,
			fn : wr[i].cb
		};
        OXCache.moduleMappings[wr[i].module].get(i, wr[i].module,
            wr[i].collection, null, wr[i].cb, true);		
    }   
    OXCache.cachedObjectsTrash=new LRUKeyList();
    OXCache.cachedCollectionsTrash=new Array();
	OXCache.requested = {
        CNOT: [], CEXP: [], CMIS: {}, LNOT: {}, LEXP: {}, LMIS: {}
    };
});
OXCache.errorHandler = function(uniqueName,request,response,response2) {
	if(debug) { console.debug("Error",uniqueName,request,response,response2); }
	if(response.error) {
		switch(response.category) {
			case  1: //USER IMPUT (SHOULD NOT EXIST) NOT IN CACHE
			    newServerError(response);
                OXCache.unregister(uniqueName);				
                break;
			case  2: //CONFIGURATION ERROR (SHOULD NOT EXIST) NOT IN CACHE
			    newServerError(response);
				OXCache.unregister(uniqueName);
                break;
			case  3: //MISSING RIGHTS
            case  4: //TRY AGAIN
			case  5: //SUBSYSTEM DOWN
			    newServerError(response);
                var col=OXCache.cachedCollections.get(uniqueName);
				if(col) { 
				    col.check=false;
					col.modified=false;
				} else {
					OXCache.unregister(uniqueName);
				}
				break;
			case  6: //SOCKET CONNECTION (CAN'T OCCUR)
                newServerError(response);
				OXCache.unregister(uniqueName);
				break;
			case  7: //INTERNAL ERROR (SERVER BUG)
                newServerError(response);
				OXCache.unregister(uniqueName);
				break;
			case  8: //CODE ERROR (SERVER BUG)
                newServerError(response);
				OXCache.unregister(uniqueName);
				break;
			case  9: //MODIFICATION CHANGES(TIMESTAMP EXPIRED)
                newServerError(response);
				OXCache.unregister(uniqueName);
				break;
			case 10: //CONFIG FILE ERROR(SERVER BUG)
                newServerError(response);
				OXCache.unregister(uniqueName);
				break;
			case 11: //RESSOURCE FULL e.g IMAP QUOTA
                newServerError(response);
				OXCache.unregister(uniqueName);
				break;
			case 12: //TO LONG VALUES IN FIELD (SHOULD NOT EXIST)
                newServerError(response);
				OXCache.unregister(uniqueName);
				break;
			case 13: //WARNING
                newServerError(response);
				OXCache.unregister(uniqueName);
                break;
			default:
			    newServerError(response);
                break;
		}
	}
	return;	
}
/**
* @private
* Starts a new JSON Request combined with OXCache join handling 
* @param url {String} Url of request
* @param data {object} Data of request
* @param cb {function} Callback function
*/
OXCache.getJSON = function(url,data,cb) {
    (new JSON()).put(url,data,null,OXCache.join.add(cb), OXCache.join.alt());   
}
/**
* @private
* Sets a new Mapping for ObjectCache 
* @param url {String} module of mapping
* @param data {OXMapping} An OX Mapping
*/
OXCache.setMapping = function(module,mapping) {
    if(OXCache.moduleMappings[module]) {
        if(debug) { alert("A module mapping for module >"+module+"< is still available in Object cache."); }
        return;
    } 
    OXCache.moduleMappings[module]=mapping;
}
/**
* Starts a OXCache request 
* @param uniqueName {String} Unique name for this request 
* @param module {String} Module of the request
* @param collection {OXCollection} see OXCollection for further description
* @param callbackModified {function} Callback Modified will be executed when Collection is modified (optional)
* @param callback {function} Callback which will be executed directly (optional)
* @param forceupdate {boolean} (optional) Ignore expire handling of object and start new request automatically (optional)
*        default <code>false</code>
* @param errorHandler {function} An optional callback function which is called instead of the default callback in the case of an error.
*        The error itself is handled by the cache.
*/
OXCache.newRequest = function (uniqueName,module,collection,callbackModified,callback,forceupdate, errorHandler) {
    var returnobject;
	uniqueName="oxcache_external_"+OXCache.getRequestSequence();
	if(!OXCache.moduleMappings[module]) {
        if(debug) { alert("A module mapping for module >"+module+"< is not available in Object cache.");}
        return;
    }
	if(!uniqueName) {
        if(debug) { alert("No uniquename entered for modified function");}
        return;
    }
    if((OXCache.modifiedCallbacks[uniqueName] && 
        OXCache.modifiedCallbacks[uniqueName].fn && 
        callbackModified) || 
	   (OXCache.actualCallbacks[uniqueName] && 
       OXCache.actualCallbacks[uniqueName].fn && 
       callback))
	{
        if(debug) { alert("A modified function for name >"+uniqueName+"< is still available in Object cache.");}
    }
	if (callbackModified) {
	   var modCB = OXCache.modifiedCallbacks[uniqueName] =
	       { module: module, collection: collection };
	   modCB.fn = callbackModified.fn || callbackModified;
	   if (callbackModified.before) {
           modCB.before = callbackModified.before;
	   }
	}
	if(callback) {
	   OXCache.actualCallbacks[uniqueName] = {
	       module : module,
	       collection: collection,
	       fn: callback,
	       errorHandler: errorHandler
       };
	}
	var returnobject=OXCache.moduleMappings[module].get(uniqueName,module,collection,callbackModified,callback,forceupdate);
	returnobject.uniqueName=uniqueName;
	return returnobject;
}

/**
* Unregister a modified callback 
* @param uniqueName {String} Unique name of the modified request
* */
OXCache.unregister = function(uniqueName) {
    var registered=false
	if(OXCache.modifiedCallbacks[uniqueName]) { 
        delete OXCache.modifiedCallbacks[uniqueName];  
		registered=true;
    }
    var callback = OXCache.actualCallbacks[uniqueName];
	if(callback) {
	    if (callback.errorHandler) {
	        callback.fn = callback.errorHandler;
	    } else {
            delete OXCache.actualCallbacks[uniqueName];
	    }
		registered=true;
    } 
	//if(debug && !registered) { alert("No function for name >"+uniqueName+"< is not available in Object cache.");}
    
}
/**
* Updates all modified callbacks 
* */

OXCache.update = function() {
    for(var counter in OXCache.modifiedCallbacks) {
        var uniqueObject=OXCache.newRequest(null,OXCache.modifiedCallbacks[counter].module,OXCache.modifiedCallbacks[counter].collection,null,function(data) {
	        if (uniqueObject) {
			  OXCache.unregister(uniqueObject.uniqueName);
			} else {
			    //debugger;
			}
	    },true);
    }   
}
  /////////////////////////////////////
 //   OX Mapping                    //
/////////////////////////////////////
/**
 * Abstract Mapping which has implemented functions for whole core modules
 */
function OXAbstractMapping() {}
/**
 * Returns function get for a OXMapping implementation.  
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} get function for OXMapping implementation. Handles requests of a module type in combination if object cache.
 * params of the function:
 *      uniqueName {String} Unique name of a request
 *      module {String} Module of a request
 *      collection {OXCollection} OXCollection of a request see OXCollection for further description
 *      (optional) callbackModified {function} A callback which has to be executed when Collection is modified 
 *      (optional) callback {function} A callback which will be executed directly 
 *      (optional) forceupdate {boolean}  A boolean ig expire handling of OXCache ought to be ignored. 
 *          
 */
OXAbstractMapping.get=function(oxMapping) {
    return function(uniquename,module,collection,callbackModified,callback,forceupdate) {
        //Check criteria search or list search
        if(collection.criteria) {
            oxMapping.criteriaRequest(uniquename,module,collection,callbackModified,callback,forceupdate);
        } else if(collection.objects) {
            return oxMapping.listRequest(uniquename,module,collection,callbackModified,callback,forceupdate);
        } else {
            if(debug) { alert("Illegal request: Neighter criteria nor objects are set"); }
        }
		return {};
    }
}
/**
 * Returns Execute function a OXMapping implementation.
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} execute function for OXMapping implementation. Excutes all needed callbacks of an module in OXCache
 *          
 */
OXAbstractMapping.execute=function(oxMapping) {
	return function() {
		for(var i in OXCache.actualCallbacks) {
			if(OXCache.callbackArray[i]) {
			   var tmpCallback=OXCache.actualCallbacks[i];
                if(!tmpCallback.executed) {
					tmpCallback.executed=true;
					if(tmpCallback.module==oxMapping.module) {
	                    var ret=oxMapping.buildResponse(tmpCallback,false,i);
	                    if(ret) {
	                        OXCache.actualCallbacks[i].fn(ret);
	                    }
	                }
					tmpCallback.executed=false;
			    }	
			}    
        }
		for(var i in OXCache.modifiedCallbacks) {
            if(OXCache.actualCallbacks[i]) { continue; }
			if(OXCache.modifiedArray[i]) {
				var tmpCallback=OXCache.modifiedCallbacks[i];
                if(!tmpCallback.executed) {
					tmpCallback.executed=true;
		            if(tmpCallback.module==oxMapping.module) {
		                var ret=oxMapping.buildResponse(tmpCallback,true,i);
						if(ret) {
							OXCache.modifiedCallbacks[i].fn(ret);	
						}
		            }
					tmpCallback.executed=false;
				}
			}
        }
	}
}
/**
 * @private 
 * This function is needed, if abstract function execute of an OXAbstractMapping is mapped to an OXMapping implementation
 * Return function builds response object, for a callback
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} buildResponse function for OXMapping implementation. 
 * params of the function:
 *      collection {OXCollection} OXCollection of an response
 *      isModified {boolean} boolean if callback is modified request
 *      callback {boolean} executable Callback          
 */
OXAbstractMapping.buildResponse = function(oxMapping) {
	return function(tmpCallback,isModified,uniqueName) {
		var collection=tmpCallback.collection;
		if(collection.criteria) {
			var tmpCollection=oxMapping.getCollection(collection,false,true);
			if(!tmpCollection || tmpCollection.check) {
				if(debug) { console.debug("Missing or changed collection:",collection); }
				OXCache.addRequest(oxMapping.module,collection,tmpCallback.fn,uniqueName);
                if (tmpCollection) tmpCollection.check = false;
			} else if(!isModified || tmpCollection.modified) {
                var retCol=new OXCollection();
				retCol.timestamp=tmpCollection.timestamp;
                retCol.criteria=tmpCollection.criteria;
				retCol.search=tmpCollection.search;
				//TODO CHECK FOR ORDER 
                retCol.order=collection.order;         //Order
                var direction=1;
                if(collection.order && collection.order.length) {
					if(collection.order[0].order!=tmpCollection.order[0].order) {
						direction=-1;
					}
				} 
                retCol.objects=new Array(tmpCollection.objects.length);
                retCol.map_objects=new LRUKeyList();
				var counter= (direction==1) ? 0 : tmpCollection.objects.length-1;
				for(var i=0;i<tmpCollection.objects.length;i++) {
				   //var tmpObject=OXCache.cachedObjects.get(tmpCollection.objects[i]) || OXCache.cachedObjectsTrash.get(tmpCollection.objects[i]);
				   var tmpObject=tmpCollection.objects[i];
	               if(tmpObject) { 
	                   retCol.objects[counter]=tmpObject;
	                   retCol.map_objects.set(tmpObject,counter+1);
	               } else {
	                   if(debug) {console.debug(tmpCollection.objects[i],i,"is missing");//TODO : OBJECT IS OUT OF CACHE CAUSED BY SIZE AND MODIFIED REQUEST NEED DATA 
					   }   
	               }
				   counter=counter+direction;
			    }
			}
			return retCol;
		} else if(collection.objects) {
			if(isModified) {
				if(!tmpCallback.modified) {
					var realmodified=false;
					for(var i=0;i<OXCache.modifiedObjects.length;i++) {
						for(var i2=0;i2<collection.objects.length;i2++) {
							if(OXCache.modifiedObjects[i].equals(collection.objects[i2])) {
							  realmodified=true;
							  break;
					        }
						}	
					}
					if(!realmodified) return;
				} else {
					tmpCallback.modified=false;
				}
			}
			var retCol=new OXCollection();
            retCol.objects=new Array();
            for(var i=0;i<collection.objects.length;i++) {
                //TODO
				if(!collection.objects[i].hashCode || !collection.objects[i].equals) {
					collection.objects[i]=oxMapping.objectConstructor.createfromObject(collection.objects[i]);
				}
                var tmpObject=OXCache.cachedObjects.get(collection.objects[i]) || OXCache.cachedObjectsTrash.get(collection.objects[i]);
                if(tmpObject) {
                    if (!tmpObject.hasColumns(collection.columns)) {
                        if (debug) {
                            console.debug("Lost columns", collection.columns,
                                clone(tmpObject), getStackTrace());
                        }
                        OXCache.addRequest(oxMapping.module, collection,
                                           tmpCallback.fn, uniqueName);
                        return;
                    }
                    retCol.objects.push(tmpObject);
                    retCol.map_objects.set(tmpObject, i + 1);
                } else {
					//TODO ??? IS OBJECT REAL DELETED ??? THROW ERROR
                    if(debug) {
						console.debug(collection.objects[i],i,"is missing");//TODO : OBJECT IS OUT OF CACHE CAUSED BY SIZE AND MODIFIED REQUEST NEED DATA
					}   
                }
            }
			return retCol;	
        }
	} 
}
/**
 * @private 
 * This function is needed, if abstract function get of an OXAbstractMapping is mapped to an OXMapping implementation
 * Return function handles criteria requests of a OXMapping request
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} criteriaRequest function for OXMapping implementation. 
 *      params of the function:
 *      uniqueName {String} Unique name of a request
 *      module {String} Module of a request
 *      collection {OXCollection} OXCollection of a request see OXCollection for further description
 *      (optional) callbackModified {function} A callback which has to be executed when Collection is modified 
 *      (optional) callback {function} A callback which will be executed directly 
 *      (optional) forceupdate {boolean}  A boolean ig expire handling of OXCache ought to be ignored. 
 * 
 */
OXAbstractMapping.criteriaRequest =function(oxMapping) {
    return function(uniqueName,module,collection,callbackModified,callback,forceupdate) {
        var onlymandatory=false;
		if(!collection.columns) { 
            var tmpColumns=new Array();
            for(var value in oxMapping.stringmapping) {
                tmpColumns.push(value);
                collection.columns=tmpColumns;
            }
        }
		for(var i=0;i<collection.columns.length;i++) {
            for(var i2=0;i2<oxMapping.mandatoryfields.length;i2++) {
                if(collection.columns[i]==oxMapping.mandatoryfields[i2]) {
				    collection.columns.splice(i,1);
                }
            }
        }
		if(!collection.columns.length) {
			onlymandatory=true;
		}
        collection.columns=oxMapping.mandatoryfields.concat(collection.columns);             
		//Check criteria exists
        var myCollection=oxMapping.getCollection(collection,false);
        if(myCollection) { 
            //Check time expired
            if(!forceupdate && (myCollection.lastRequest && myCollection.lastRequest>(new Date().getTime()-OXCache.updateTime))) {
				if (!onlymandatory) {
					for (var i = 0; i < myCollection.objects.length; i++) {
						var tmpObject = OXCache.cachedObjects.get(myCollection.objects[i]);
						if (!(tmpObject && tmpObject.hasColumns(collection.columns))) {
							if (!tmpObject) {
								if (myCollection.objects[i].hashCode) {
									tmpObject = myCollection.objects[i];
								}
							}
							oxMapping.addCriteriaMissingColumn(collection.columns, tmpObject, uniqueName);
						}
					}
				}               
            } else {
                oxMapping.addCriteriaExpired(collection,myCollection.timestamp,uniqueName);
            }     
        } else {
            oxMapping.addCriteriaNotInCache(collection,uniqueName);
        }
		var tmpFn = OXCache.join.add();
        oxMapping.request();
		delete oxMapping.requestCriteriaNotInCache;
        delete oxMapping.requestCriteriaExpired;
        delete oxMapping.requestCriteriaMissingColumn;
        tmpFn();
    }
}
/**
 * @private 
 * This function is needed, if abstract function get of an OXAbstractMapping is mapped to an OXMapping implementation
 * Return function handles list requests of a OXMapping request
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} listRequest function for OXMapping implementation. 
 *      params of the function:
 *      uniqueName {String} Unique name of a request
 *      module {String} Module of a request
 *      collection {OXCollection} OXCollection of a request see OXCollection for further description
 *      (optional) callbackModified {function} A callback which has to be executed when Collection is modified 
 *      (optional) callback {function} A callback which will be executed directly 
 *      (optional) forceupdate {boolean}  A boolean if expire handling of OXCache ought to be ignored. 
 */
OXAbstractMapping.listRequest =function(oxMapping) {
    return function(uniqueName,module,collection,callbackModified,callback,forceupdate) {
        var returnobject={};
		returnobject.clear=[];
		returnobject.set=[];
		var isrequest=false;
		if(collection.objects) {
			if(!collection.columns) { 
                var cols = collection.columns = [];
                for(var value in oxMapping.stringmapping) cols.push(value);
                if (oxMapping.alternative) {
                    for(var i in oxMapping.alternative) {
                        var alt = oxMapping.alternative[i];
                        if (typeof alt != "function" || alt()) cols.push(i);
                    }
                 }
            } 
			for(var i=0;i<collection.columns.length;i++) {
                for(var i2=0;i2<oxMapping.mandatoryfields.length;i2++) {
                    if(collection.columns[i]==oxMapping.mandatoryfields[i2]) {
                        collection.columns.splice(i,1);
                    }
                }
            }
			collection.columns=oxMapping.mandatoryfields.concat(collection.columns);
            for(var i=0;i<collection.objects.length;i++) {
                var tmpVar = collection.objects[i];
                var tmpObject = oxMapping.objectConstructor.createfromObject(tmpVar);
				var myCacheObject=OXCache.cachedObjects.get(tmpObject);
                if(myCacheObject) {
                    if(myCacheObject.hasColumns(collection.columns)) {
                        if(forceupdate || (!(myCacheObject.lastRequest>(new Date().getTime()-OXCache.updateTime)))) {
                           oxMapping.addListExpired(collection.columns,tmpObject,uniqueName);
						   isrequest=true;
                        } 
						returnobject.set.push(OXCache.cachedObjects.get(tmpObject));
                    } else {
                       oxMapping.addListMissingColumn(collection.columns,tmpObject,uniqueName);
					   returnobject.clear.push(tmpObject);
					   isrequest=true;
                    }
                } else {
                    oxMapping.addListNotInCache(collection.columns,tmpObject,uniqueName);
					returnobject.clear.push(tmpObject);
					isrequest=true;
                }
           }
       }   
        var tmpFn = OXCache.join.add();
       oxMapping.request();
	   delete oxMapping.requestListNotInCache;
       delete oxMapping.requestListExpired;
       delete oxMapping.requestListMissingColumn;
        tmpFn();
		return isrequest ? returnobject : {};
    }
}
/**
 * @private 
 * This function is needed, if abstract function criteria Request of OXAbstractMapping is mapped to an OXMapping implementation
 * Return function adds OXCollection to the to server request list (not in Cache)
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} addCriteriaNotInCache function for OXMapping implementation. 
 *      params of the function:
 *      collection {OXCollection} OXCollection for a request see OXCollection for further description
 */
OXAbstractMapping.addCriteriaNotInCache = function(oxMapping) {
    return function(collection, uniqueName) {
		if(!oxMapping.requestCriteriaNotInCache) {
            oxMapping.requestCriteriaNotInCache=new Array();
        }
        var cnot = OXCache.requested.CNOT;
        for(var i=0;i<OXCache.requested["CNOT"].length;i++) {
            if(oxMapping.equalsCollection(cnot[i].collection, collection)) {
                return; 
            }
        }
        OXCache.requested["CNOT"].push({ collection : collection, uniqueName : uniqueName });
        oxMapping.requestCriteriaNotInCache.push({ collection : collection, uniqueName : uniqueName } );
    }
}
/**
 * @private 
 * This function is needed, if abstract function criteria Request of OXAbstractMapping is mapped to an OXMapping implementation
 * Return function adds OXCollection to the to server request list (in Cache but expired)
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} addCriteriaExpired function for OXMapping implementation. 
 *      params of the function:
 *      collection {OXCollection} OXCollection for a request see OXCollection for further description
 */
OXAbstractMapping.addCriteriaExpired = function(oxMapping) {
    return function(collection, last_modified, uniqueName) {
        if(!oxMapping.requestCriteriaExpired) {
            oxMapping.requestCriteriaExpired=new Array();
		}
        var cexp = OXCache.requested.CEXP;
        for(var i = 0; i < cexp; i++) {
            if (oxMapping.equalsCollection(cexp[i].collection, collection)) {
                return;
            }
        }
        cexp.push({ collection : collection, last_modified : last_modified });
        oxMapping.requestCriteriaExpired.push({ collection : collection, last_modified : last_modified, uniqueName : uniqueName });
	}
}
/**
 * @private 
 * This function is needed, if abstract function criteria Request of OXAbstractMapping is mapped to an OXMapping implementation
 * Return function adds OXObjects to the to server request list (object in Cache but columns missing)
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} addCriteriaExpired function for OXMapping implementation. 
 *      params of the function:
 *      columns {Array of Strings} Needed columsn for a request
 *      object {Key} key of an object
 */
OXAbstractMapping.addCriteriaMissingColumn = function(oxMapping) {
    return function(columns,object,uniqueName) {
        if(!oxMapping.requestCriteriaMissingColumn) {
            oxMapping.requestCriteriaMissingColumn=new Array();
		}
		var key = JSON.serialize([columns, object]);
        if (OXCache.requested.CMIS[key]) return;
        OXCache.requested.CMIS[key] = true;
        oxMapping.requestCriteriaMissingColumn.push({ columns : columns , object : object , uniqueName : uniqueName });
    }
}
/**
 * @private 
 * This function is needed, if abstract function list Request of OXAbstractMapping is mapped to an OXMapping implementation
 * Return function adds OXObjects to the to server request list (object not in Cache)
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} addListNotInCache function for OXMapping implementation. 
 *      params of the function:
 *      columns {Array of Strings} Needed columns for a request
 *      object {Key} key of an object
 */
OXAbstractMapping.addListNotInCache = function(oxMapping) {
    return function(columns,object,uniqueName) {
        if(!oxMapping.requestListNotInCache) {
            oxMapping.requestListNotInCache=new Array();
        }
        var key = JSON.serialize([columns, object]);
        if (OXCache.requested.LNOT[key]) return;
        OXCache.requested.LNOT[key] = true;
	    oxMapping.requestListNotInCache.push({ columns : columns , object : object, uniqueName : uniqueName });
    }
}
/**
 * @private 
 * This function is needed, if abstract function list Request of OXAbstractMapping is mapped to an OXMapping implementation
 * Return function adds OXObjects to the to server request list (object in Cache but expired)
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} addListExpired function for OXMapping implementation. 
 *      params of the function:
 *      columns {Array of Strings} Needed columns for a request
 *      object {Key} key of an object
 */
OXAbstractMapping.addListExpired = function(oxMapping) {
    return function(columns,object,uniqueName) {
        if(!oxMapping.requestListExpired) {
            oxMapping.requestListExpired=new Array();
        }
        var key = JSON.serialize([columns, object]);
        if (OXCache.requested.LEXP[key]) return;
        OXCache.requested.LEXP[key] = true;
        oxMapping.requestListExpired.push({ columns : columns , object : object, uniqueName : uniqueName });
    }
}
/**
 * @private 
 * This function is needed, if abstract function list Request of OXAbstractMapping is mapped to an OXMapping implementation
 * Return function adds OXObjects to the to server request list (object in Cache but missing columns)
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} addListMissingColumn function for OXMapping implementation. 
 *      params of the function:
 *      columns {Array of Strings} Needed columns for a request
 *      object {Key} key of an object
 */
OXAbstractMapping.addListMissingColumn = function(oxMapping) {
    return function(columns,object,uniqueName) {
        if(!oxMapping.requestListMissingColumn) {
            oxMapping.requestListMissingColumn=new Array();
        }
        var key = JSON.serialize([columns, object]);
        if (OXCache.requested.LMIS[key]) return;
        OXCache.requested.LMIS[key] = true;
        oxMapping.requestListMissingColumn.push({ columns : columns , object : object, uniqueName : uniqueName });
    }
}
/**
 * @private 
 * This function is needed, if abstract function list or criteria Request of OXAbstractMapping is mapped to an OXMapping implementation
 * Return function executes server requests.
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} request function for OXMapping implementation. 
 */
OXAbstractMapping.request = function(oxMapping) {
    return function() {
        var multipleRequests=new Array();
        if(oxMapping.requestCriteriaNotInCache) {
            for(var i = 0;i<oxMapping.requestCriteriaNotInCache.length;i++) {
                var tmpObject= { action : "all", module : oxMapping.module };
                var cols = oxMapping.convertColumns(oxMapping.requestCriteriaNotInCache[i].collection.columns)
                for (var j in cols) tmpObject[j] = cols[j].join(",");
                for(var counter in oxMapping.requestCriteriaNotInCache[i].collection.criteria) {
                    switch(counter) {
                        case "folder_id":
                           tmpObject["folder"]= oxMapping.requestCriteriaNotInCache[i].collection.criteria[counter];
                           break;
                        default:
                            tmpObject[counter]= oxMapping.requestCriteriaNotInCache[i].collection.criteria[counter];
                    }
                }
				if(oxMapping.requestCriteriaNotInCache[i].collection.search) {
					var searchObject=oxMapping.requestCriteriaNotInCache[i].collection.search;
					var operator="or";
					switch(oxMapping.module) {
                        case "tasks" :
                           for(var key in searchObject) {
                                var value=searchObject[key];
                                if(key.toLowerCase() == "operator" && value.toLowerCase() == "or") {
                                    operator="or";
                                } else if(key == "title" || key =="categories" || key =="note") {
                                    tmpObject.action="search";
                                    tmpObject.data = { pattern : value };    
                                    break;
                                } else {
                                    alert("This search" + searchObject +"is not supported for tasks");
                                }
                            }
                            break;
                        case "contacts" :
                            if(searchObject["startletter"]) {
                                if(searchObject["last_name"]) {
                                    tmpObject.action="search";
                                    tmpObject.data = { pattern : searchObject["last_name"] , startletter : true };
                                    if(tmpObject.folder) { tmpObject.data["folder"] = tmpObject.folder }
                                } else {
                                    alert("This search" + searchObject +"is not supported for contacts")
                                } 
                            } else {
                                tmpObject.action="search";
                                tmpObject.data = {};
                                if(tmpObject.folder) {
                                    tmpObject.data.folder = tmpObject.folder;
                                }
                                for(var key in searchObject) {
                                    var value=searchObject[key];
                                    if(key.toLowerCase() == "operator" && value.toLowerCase() == "or") {
                                        tmpObject.data.orSearch = true;
                                    } else if (key in { email1: 1, email2: 1,
                                                        email3: 1, display_name: 1,
                                                        first_name: 1, last_name: 1,
                                                        company: 1, categories: 1,
                                                        city_business: 1, street_business: 1,
                                                        department: 1 })
                                    {
                                        tmpObject.data[key] = value;
                                    } else {
                                        alert("This search" + searchObject +"is not supported for contacts");
                                    }
                                }   
                            }
						    break;
						case "mail":
                           for(var key in searchObject) {
                                var value=searchObject[key];
                                if(key.toLowerCase() == "operator" && value.toLowerCase() == "or") {
                                    operator="or";
                                } else {
                                    tmpObject.action="search";
                                    if(!tmpObject.data) { tmpObject.data=[]; }
									var tmpCol=oxMapping.convertColumns([trimStr(key)]).columns[0];
                                    if(tmpCol) { tmpObject.data.push({col:  parseInt(tmpCol), pattern : searchObject[key] }); }
                                }    
                            }
                            break;
                        case "infostore":
                            for(var key in searchObject) {
                                var value=searchObject[key];
                                if(key.toLowerCase() == "operator" && value.toLowerCase() == "or") {
                                    operator="or";
                                } else if(key == "title" || key =="url" || key =="filename" || key =="version_comment" || key=="categories" || key=="description") {
                                    tmpObject.action="search";
                                    tmpObject.data = { pattern : value };    
                                    break;
                                } else {
                                    alert("This search" + searchObject +"is not supported for tasks");
                                }
							}
                            break;
                    }
				}
				if(oxMapping.requestCriteriaNotInCache[i].collection.order){
                    var tmpOrder=oxMapping.requestCriteriaNotInCache[i].collection.order[0];
                    if(oxMapping.convertColumns([trimStr(tmpOrder.sort)]).columns[0]) {
                       tmpObject["sort"]=oxMapping.convertColumns([trimStr(tmpOrder.sort)]).columns[0];
                       tmpObject["order"]= (tmpOrder.order.toLowerCase()=="desc") ? "desc" : "asc";
                    }                   
                }
                multipleRequests.push(tmpObject);
            }
        }
        if(oxMapping.requestCriteriaExpired) {
            for(var i = 0;i<oxMapping.requestCriteriaExpired.length;i++) {
                var tmpObject= { action : "all", module : oxMapping.module };
                var cols = oxMapping.convertColumns(oxMapping.requestCriteriaExpired[i].collection.columns);
                for (var j in cols) tmpObject[j] = cols[j].join(",");
                var tmpObject2= {
                    action : "updates", module : oxMapping.module, timestamp : oxMapping.requestCriteriaExpired[i].last_modified || 0,
                    ignore : "deleted"
                };
                var cols = oxMapping.convertColumns(oxMapping.requestCriteriaExpired[i].collection.columns);
                for (var j in cols) tmpObject2[j] = cols[j].join(",");
                for(var counter in oxMapping.requestCriteriaExpired[i].collection.criteria) {
                    switch(counter) {
                        case "folder_id":
                           tmpObject["folder"]= oxMapping.requestCriteriaExpired[i].collection.criteria[counter];
                           tmpObject2["folder"]= oxMapping.requestCriteriaExpired[i].collection.criteria[counter];
                           break;
                        default:
                            tmpObject[counter]= oxMapping.requestCriteriaExpired[i].collection.criteria[counter];
                            tmpObject2[counter]= oxMapping.requestCriteriaExpired[i].collection.criteria[counter];
                    }
                }
				if(oxMapping.requestCriteriaExpired[i].collection.search) {
                    var searchObject=oxMapping.requestCriteriaExpired[i].collection.search;
					var operator="or";
                    switch(oxMapping.module) {
                        case "tasks" :
                           for(var key in oxMapping.requestCriteriaExpired[i].collection.search) {
                                var value=oxMapping.requestCriteriaExpired[i].collection.search[key];
                                if(key.toLowerCase() == "operator" && value.toLowerCase() == "or") {
                                    operator="or";
                                } else if(key == "title" || key =="categories" || key =="note") {
                                    tmpObject.action="search";
                                    tmpObject.data = { pattern : value };  
									if(tmpObject.folder) { tmpObject.data["folder"] = tmpObject.folder }  
                                } else {
                                    alert("This search" + oxMapping.requestCriteriaExpired[i].collection.search +"is not supported for tasks");
                                }
                            }
                            break;
						case "contacts" :
                           if(searchObject["startletter"]) {
                                if(searchObject["last_name"]) {
                                    tmpObject.action="search";
                                    tmpObject.data = { pattern : searchObject["last_name"] , startletter : true };
                                    if(tmpObject.folder) { tmpObject.data["folder"] = tmpObject.folder }
                                } else {
                                    alert("This search" + searchObject +"is not supported for contacts")
                                } 
                            } else {
                                tmpObject.action="search";
                                tmpObject.data = {};
                                if(tmpObject.folder) { tmpObject.data["folder"] = tmpObject.folder }
                                for(var key in searchObject) {
                                    var value=searchObject[key];
                                    if(key.toLowerCase() == "operator" && value.toLowerCase() == "or") {
                                        tmpObject.data.orSearch = true;
                                    } else if(key in { email1: 1, email2: 1,
                                                       email3: 1, display_name: 1,
                                                       first_name: 1, last_name: 1,
                                                       company: 1, categories: 1,
                                                       city_business: 1, street_business: 1,
                                                       department: 1 })
                                    {
                                        tmpObject.data[key] = value;
                                    } else {
                                        alert("This search" + searchObject +"is not supported for contacts");
                                    }
                                }   
                            }
                            break;
						case "mail":
                           for(var key in searchObject) {
                                var value=searchObject[key];
                                if(key.toLowerCase() == "operator" && value.toLowerCase() == "or") {
                                    operator="or";
                                } else {
                                    tmpObject.action="search";
                                    if(!tmpObject.data) { tmpObject.data=[]; }
                                    var tmpCol=oxMapping.convertColumns([trimStr(key)]).columns[0];
                                    if(tmpCol) { tmpObject.data.push({col:  parseInt(tmpCol), pattern : searchObject[key] }); }
                                }    
                            }
                            break;
						case "infostore":
                            for(var key in searchObject) {
                                var value=searchObject[key];
                                if(key.toLowerCase() == "operator" && value.toLowerCase() == "or") {
                                    operator="or";
                                } else if(key == "title" || key =="url" || key =="filename" || key =="version_comment" || key=="categories" || key=="description") {
                                    tmpObject.action="search";
                                    tmpObject.data = { pattern : value };    
                                    break;
                                } else {
                                    alert("This search" + searchObject +"is not supported for tasks");
                                }
							}
                            break;
                    }
                }
				if(oxMapping.requestCriteriaExpired[i].collection.order){
                    var tmpOrder=oxMapping.requestCriteriaExpired[i].collection.order[0];
                    if(oxMapping.convertColumns([trimStr(tmpOrder.sort)]).columns[0]) {
                       tmpObject["sort"]=oxMapping.convertColumns([trimStr(tmpOrder.sort)]).columns[0];
                       tmpObject["order"]= (tmpOrder.order.toLowerCase()=="desc") ? "desc" : "asc";
                    }                   
                }
                
                multipleRequests.push(tmpObject);
				if(!tmpObject.search) {
                    multipleRequests.push(tmpObject2);
				}
            }
        }
        if(oxMapping.requestCriteriaMissingColumn && oxMapping.requestCriteriaMissingColumn.length) {
            var tmpData= [];
            var tmpColumns;
            var tmpObject= { action : "list", module : oxMapping.module, data : tmpData };
            for(var i = 0;i<oxMapping.requestCriteriaMissingColumn.length;i++) {
                if(!tmpColumns) {
                    tmpColumns = oxMapping.convertColumns(oxMapping.requestCriteriaMissingColumn[i].columns);
                    for (var j in tmpColumns) tmpObject[j] = tmpColumns[j].join(",");           
                }
                var tmpString=oxMapping.requestCriteriaMissingColumn[i].object;
                tmpData.push({ folder : tmpString.folder_id || tmpString.folder,
                               id : tmpString.id});
            }
            multipleRequests.push(tmpObject);
        }
        processList(oxMapping.requestListNotInCache);
        processList(oxMapping.requestListExpired);
        processList(oxMapping.requestListMissingColumn);
        function processList(list) {
            if (!list || !list.length) return;
            var tmpData= [];
            var tmpColumns;
			var tmpAction="list";
			var param = { view: "text" };
            for(var i = 0; i < list.length; i++) {
                if(!tmpColumns) {
                    tmpColumns = oxMapping.convertColumns(list[i].columns);
                    for (var i2 = 0; i2 < tmpColumns.columns.length; i2++) {
                        if (!tmpColumns.columns[i2]) {
                            tmpAction="get";
                            list[i].get = true;
                            var stringcolumn = list[i].columns[i2];
                            if (stringcolumn == "attachments_plain") { param.view="text"; }
                            else if (stringcolumn == "attachments_html") { param.view="html"; }
                            else if (stringcolumn == "attachments_html_noimage") { param.view="noimg"; }
                            else if (stringcolumn == "unseen") { param.unseen = "true"; }
                        }
                    }
                }
			    var tmpString=list[i].object;
                if(tmpAction=="get") {
                    var tmpObject= { action : tmpAction, module : oxMapping.module, id : tmpString.id, folder : tmpString.folder_id || tmpString.folder };
                    for (var i3 in param) {
                        tmpObject[i3] = param[i3]; 
                    }
                    multipleRequests.push(tmpObject); 
                } else {
                    var fields = {};
                	fields.folder = tmpString.folder_id || tmpString.folder;
                	fields.id = tmpString.id;
                    if (tmpString.recurrence_position != undefined) {
                    	fields.recurrence_position = tmpString.recurrence_position;
                    }
                    tmpData.push(fields);
                }
            }
            if(tmpAction=="list") {
                var tmpObject= { action: tmpAction, module: oxMapping.module,
                                 data: tmpData };
                for (var i in tmpColumns) tmpObject[i] = tmpColumns[i].join();
				multipleRequests.push(tmpObject);
            }
        }
	    var tmpFn=OXCache.join.add();
		if(multipleRequests.length) {
			if(debug) { console.debug("Requests",multipleRequests); }
			(function (
                requestCriteriaNotInCache,
                requestCriteriaExpired,
                requestCriteriaMissingColumn,
                requestListNotInCache,
                requestListExpired,
                requestListMissingColumn) 
            {
           
                tmptime=new Date().getTime();
                OXCache.getJSON(AjaxRoot + "/multiple?session="+session +"&continue=true", multipleRequests, function(response) {
                   oxMapping.handleResponse(response,requestCriteriaNotInCache,requestCriteriaExpired,requestCriteriaMissingColumn,requestListNotInCache,requestListExpired,requestListMissingColumn);
                });
            })(clone(oxMapping.requestCriteriaNotInCache),clone(oxMapping.requestCriteriaExpired),
               clone(oxMapping.requestCriteriaMissingColumn),clone(oxMapping.requestListNotInCache),
               clone(oxMapping.requestListExpired),clone(oxMapping.requestListMissingColumn));
       }
	   tmpFn();
    }
}
/**
 * @private 
 * This function is needed, if abstract function request of OXAbstractMapping is mapped to an OXMapping implementation
 * Return function handles response of server.
 * @param oxMapping {OXMapping} OXMapping implementation
 * @return {function} handleResponse function for OXMapping implementation. 
 *      params of the function:
 *      uniqueName {Object} Response object of the server
 *      requestCriteriaNotInCache {Array} Array of Criteria Requests not in Cache.
 *      requestCriteriaExpired {Array} Array of Criteria Requests which are expired.
 *      requestCriteriaMissingColumn {Array} Array of Objects in Criteria Requests with missing columns.
 *      requestListNotInCache {Array} Array of Objects in List Requests which are not in Cache.
 *      requestListExpired {Array} Array of Objects in List Requests which are in Cache but expired.
 *      requestListMissingColumn {Array} Array of Objects in List Requests which are in Cache but have missing columns.
 */
 OXAbstractMapping.handleResponse = function(oxMapping) {
	return function(response,requestCriteriaNotInCache,requestCriteriaExpired,requestCriteriaMissingColumn,requestListNotInCache,requestListExpired,requestListMissingColumn) {
		tmptime=new Date().getTime();
        
        // Execute every registered "before" callback.
		// TODO: determine which of them actually need to be called.
		for (var i in OXCache.modifiedCallbacks) {
            var mc = OXCache.modifiedCallbacks[i];
            if (mc && mc.module == oxMapping.module && mc.before) mc.before();
        }
        
		// shouldn't happen - but just in case the reponse is emtpy!?
		if (!response) {
			// ok, server fails - to prevent js errors we fake an error here
			response = [{"code":"UII-CA001","error_id":"-internal-","category":6,"error_params":[],"error":_("Unexpected: Unable to load data because the server response was empty! Please try again later or contact the system administrator!")}];
		}
		
        var count=0;
		for(var i=0;requestCriteriaNotInCache && i<requestCriteriaNotInCache.length;i++) {
            oxMapping.handleCriteriaNotInCache(requestCriteriaNotInCache[i].collection,response[count],requestCriteriaNotInCache[i].uniqueName);
            count++;
        }
        for(var i=0;requestCriteriaExpired && i<requestCriteriaExpired.length;i++) {
			if(requestCriteriaExpired[i].collection.search) {
				oxMapping.handleCriteriaExpired(requestCriteriaExpired[i].collection,response[count],null,requestCriteriaExpired[i].uniqueName);
				count++;
			} else {
                oxMapping.handleCriteriaExpired(requestCriteriaExpired[i].collection,response[count],response[count+1],requestCriteriaExpired[i].uniqueName);
                count++;		
                count++;
			}
        }
        if(requestCriteriaMissingColumn && requestCriteriaMissingColumn.length) {
            oxMapping.handleCriteriaMissingColumn(requestCriteriaMissingColumn,response[count],requestCriteriaMissingColumn.uniqueName);
            count++;
        }      
        processList(requestListNotInCache, oxMapping.handleListNotInCache);
        processList(requestListExpired, oxMapping.handleListExpired);
        processList(requestListMissingColumn, oxMapping.handleListMissingColumn);
        function processList(list, handler) {
    		if (!list || !list.length) return;
            if (list[0].get) {
                for (var i = 0; i < list.length; i++) {
                    handler.call(oxMapping, list[i], response[count],
                        list[i].uniqueName);
                    count++;
                }
    		} else {
                handler.call(oxMapping, list, response[count], list[0].uniqueName);
                count++;    
    		}
        }
        
		if (response[count] && response[count].error) {
			newServerError(response[count].error);
		}
        
	} 
}
OXAbstractMapping.handleCriteriaNotInCache = function(oxMapping) {
    return function(request,response,uniqueName) {
		if(debug) { console.debug("Criteria not in cache:",request,response,uniqueName);	}
		oxMapping.handleCriteria(request,response,null,uniqueName);
    }
}
OXAbstractMapping.handleCriteriaExpired = function(oxMapping) {
    return function(request,response,response2,uniqueName) {
        if(debug) { console.debug("Criteria expired:",request,response,response2,uniqueName); }
		oxMapping.handleCriteria(request,response,response2,uniqueName);
    }
}
OXAbstractMapping.handleCriteria= function (oxMapping) {
    return function(request,response,response2,uniqueName) {
        var tmpCollection=oxMapping.getCollection(request,false);
        if(response && response.error || response2 && response2.error) {
            //OXCache.errorHandler(uniqueName,request,response,response2);
            oxMapping.errorHandler(uniqueName,request,response,response2);
            if (tmpCollection) return;
            response = { data: [], timestamp: 0 };
        } 
        if (response.data.length > OXCache.objectSize) {
            OXCache.cachedObjects.free +=   response.data.length
                                          - OXCache.objectSize;
            OXCache.objectSize = response.data.length;
        }
        if(!tmpCollection) {
            var tmpCollection=new OXCollection();
            tmpCollection.criteria=request.criteria;
			tmpCollection.search=request.search
            tmpCollection.order=request.order;
            tmpCollection.last_modified=response.timestamp;
			tmpCollection.timestamp=response.timestamp;
            tmpCollection.lastRequest=new Date().getTime();
            tmpCollection.objects=new Array(response.data.length);
            tmpCollection.map_objects=new LRUKeyList();
			tmpCollection.modified=true;
            for(var i=0;i<response.data.length;i++) {
                var tmpObject=oxMapping.createKeyFromData(response.data[i],response["timestamp"]);
                tmpCollection.objects[i]=tmpObject;
                tmpCollection.map_objects.set(tmpObject,i+1);
                oxMapping.createCacheObject(request.columns,response.data[i],response["timestamp"],tmpObject);
            }
            oxMapping.checkAddCriteria(tmpCollection.objects,request);
            var tmp=OXCache.cachedCollections.set(OXCache.getSequence(),tmpCollection);
            if(tmp) {
                OXCache.cachedCollectionsTrash.push({ data: tmp });
            }
            tmpCollection.check=false;
        } else {
            //TIMESTAMP CAN NOT CHECK LIST EQUALS CACHED LIST
            var tmpList=new LRUKeyList(response.data.length);
            var tmpArray=new Array(response.data.length);
            var changed=false;
            for(var i=0;i<response.data.length;i++) {
                var tmpObject =oxMapping.createKeyFromData(response.data[i],response.timestamp);
                tmpList.set(tmpObject,i+1);
                tmpArray[i]=tmpObject; 
            }
            if(response2 && response2.data.length) {
                for(var i=0;i<response2.data.length;i++) {
                    var tmpObject =oxMapping.createKeyFromData(response2.data[i],response2.timestamp);
                    oxMapping.createCacheObject(request.columns,response2.data[i],response2["timestamp"],tmpObject,true,tmpCollection);
                    changed=true;
                }  
            }
            var toDelete = [];
            for(var i=0;i<tmpCollection.objects.length;i++) {
                //DELETE
				if(!tmpList.get(tmpCollection.objects[i])) {
					changed=true;
					toDelete.push(tmpCollection.objects[i]);
                }
			}
            if (toDelete.length) oxMapping.deleteObjectsInternal(toDelete);
		    if(oxMapping.module=="mail" || request.search) {
			     // NEW
				 for(var i=0;i<tmpArray.length;i++) {
				    if(!tmpCollection.map_objects.get(tmpArray[i])) {
					   oxMapping.createCacheObject(request.columns,response.data[i],response["timestamp"],tmpArray[i],true,tmpCollection);
                       changed=true;
					} 
				}
				//UPDATE
				//NOT POSSIBLE
			}
            tmpCollection.objects=tmpArray;
            tmpCollection.order=request.order;
            tmpCollection.map_objects=tmpList;
			if(changed) {
                tmpCollection.last_modified=response.timestamp;
				tmpCollection.timestamp=response.timestamp;
                tmpCollection.modified=true;
            }
			tmpCollection.check=false;
            tmpCollection.lastRequest=new Date().getTime();
        }
    }
}
OXAbstractMapping.checkAddCriteria=function(oxMapping) {
    return function(objects,request) {
        var collections=oxMapping.getSubCollections(request);
        for(var i=0;i<collections.length;i++) {
			if(!oxMapping.equalsCollection(collections[i],request)) {
                if(!collections[i].check) {
                    for(var i2=0;i2<objects.length;i2++) {
                        if(collections[i].map_objects.get(objects[i2])) {
						    collections[i].check=true;
                            break;
                        }
                    }
                }
            }
		}
    }
}
OXAbstractMapping.checkAddEditDeleteObject=function(oxMapping) {
    return function(object,executeCollection) {
        var collections=oxMapping.getSubCollectionsfromObject(object,true);
		for(var i=0;i<collections.length;i++) {
		   if(!executeCollection || !oxMapping.equalsCollection(collections[i],executeCollection)) {
		      if(!collections[i].check) {
                  collections[i].check=true;
              }
		   }
		}
		for(var i in OXCache.modifiedCallbacks) {
            if(OXCache.modifiedCallbacks[i].module == oxMapping.module && !(OXCache.modifiedCallbacks[i].collection.criteria)) {
                for(var count=0;count<OXCache.modifiedCallbacks[i].collection.objects.length;count++) {
                    if(object.equals(OXCache.modifiedCallbacks[i].collection.objects[count])) {
                        OXCache.modifiedCallbacks[i].modified=true;
                        break;                 
                    }
                }
            }
        }
    }
}

OXAbstractMapping.handleCriteriaMissingColumn = function(oxMapping) {
    return function(request,response,uniqueName) {
		if(debug) { console.debug("Criteria missing column:",request,response,uniqueName); }
		oxMapping.handleList(request,response,uniqueName);
    }
}
OXAbstractMapping.handleListNotInCache = function(oxMapping) {
    return function(request,response,uniqueName) {
	    if(debug) { console.debug("Objects not in Cache:",request,response,uniqueName); }
		oxMapping.handleList(request,response,uniqueName);
    }
}
OXAbstractMapping.handleListExpired = function(oxMapping) {
    return function(request,response,uniqueName) {
        if(debug) { console.debug("Objects expired:",request,response,uniqueName); }
		oxMapping.handleList(request,response,uniqueName);
    }
}
OXAbstractMapping.handleListMissingColumn = function(oxMapping) {
    return function(request,response,uniqueName) {
      if(debug) { console.debug("Objects columns missing:",request,response,uniqueName, getStackTrace()); }
	  oxMapping.handleList(request,response,uniqueName);
	}
}
OXAbstractMapping.handleList = function(oxMapping) {
	return function(request,response,uniqueName) {
	   if(response && response.error) {
	   	    //OXCache.errorHandler(uniqueName,request,response);
	   	    oxMapping.errorHandler(uniqueName,request,response);
            return;
	   }
	   if(response.data && response.data.constructor == Array) {
	       if(request.length==response.data.length) {
	           for(var i=0;i<request.length;i++) {
                   oxMapping.createCacheObject(request[i].columns,
                    response.data[i], response.timestamp, 0, true);
	           }
	        }  else {
	            var toDelete = [];
	            for(var i=0;i<request.length;i++) {
	                var deleted=true;
	                for(var i2=0;i2<response.data.length;i2++) {
	                    var key=oxMapping.createKeyFromData(response.data[i2]);
	                    if (   (key.folder_id || key.folder) ==
	                           (   request[i].object.folder_id
	                            || request[i].object.folder)
	                        && key.id == request[i].object.id
	                        && (request[i].object.recurrence_position || 0) ==
	                           (key.recurrence_position || 0))
	                    {
	                        deleted=false;
                            oxMapping.createCacheObject(request[i].columns,
                                response.data[i2],response.timestamp, 0, true);
	                        break;
	                    }   
	                }
	                if (deleted) toDelete.push(request[i].object);
	            }
                if (toDelete.length) oxMapping.deleteObjectsInternal(toDelete);
	        }
		} else if (response.data) {
			for(var i in request.object) {
				if(response.data[i] === undefined) { response.data[i]=request.object[i]; }
			}
            oxMapping.createCacheObject(request.columns, response.data,
                response.timestamp, 0, true);
		} else {
			// fatal error. something's completely broken on the server.
			triggerEvent("OX_New_Error", 4, _("Internal Fatal Error: Server response contains no or invalid data. Some functions may be disabled or not useable. (OC02=" + request + ")"));
		}
	}
}
OXAbstractMapping.createCacheObject = function(oxMapping) {
    return function(columns,data,timestamp,key,events,executeCollection) {
		var itsupdate=false;
		var seen=false; //MAIL ONLY
		if(!key) {
		  if(data.constructor == Array) {
		      key=oxMapping.createKeyFromData(data);
		  } else {
		  	  key=oxMapping.createKeyFromObject(data);
		  }
		}
		var tmpObject=key;
		var oldObject=OXCache.cachedObjects.get(key);
		if(!oldObject) {
		  //NEWOBJECT
          if(data.constructor == Array) { 
              for(var i=0;i<columns.length;i++) {
                    var path = columns[i].split("/");
                    if (path.length == 1) {
                        tmpObject[columns[i]] = data[i];
                    } else {
                        var subObj = tmpObject[path[0]];
                        if (!subObj) subObj = tmpObject[path[0]] = {};
                        subObj[path[1]] = data[i];
                    }
              }
          } else {
              for(var i=0;i<columns.length;i++) {
                  if(data[columns[i]]|| data[columns[i]]==0 || data[columns[i]]=="") { tmpObject[columns[i]]=data[columns[i]] }
                  else if(data[columns[i]] === undefined) {
				  	if ((oxMapping.module == "mail") && (columns[i] == "attachments_plain" || columns[i] == "attachments_html" || columns[i] == "attachments_html_noimage")) {
                        tmpObject[columns[i]] = data["attachments"] || null;
						//IMAGES ARE BLOCKED
						if(data["modified"] && data["modified"] == 1) {
                            tmpObject["blocked_images"]=true;
                        } else {
                            tmpObject["blocked_images"]=false;
                        }
                    } else if(columns[i]=="blocked_images") {
                    } else {
                        tmpObject[columns[i]] = null;
                    } 
				  } 
              }
              if (   oxMapping.module == "mail"
                  && (data["unread"] || data["unread"]== 0))
              {
                  // was localUpdate
                  ox.api.folder.update({
                      local: true,
                      folder: tmpObject.folder_id || tmpObject.folder,
                      data: {
                          unread: data.unread
                      }
                  });
              }
          }
          tmpObject["timestamp"]=timestamp;
          tmpObject.lastRequest=new Date().getTime();
          var tmp=OXCache.cachedObjects.set(tmpObject,tmpObject);
		  if(tmp) { OXCache.cachedObjectsTrash.set(tmp.key,tmp.data); }
		  if(events) {
             oxMapping.checkAddEditDeleteObject(tmpObject,executeCollection); 
          }
          if (   oxMapping.module == "mail"
              && (data["unread"] || data["unread"]== 0))
          {
              // was localUpdate
              ox.api.folder.update({
                  local: true,
                  folder: tmpObject.folder_id || tmpObject.folder,
                  data: {
                      unread: data.unread
                  }
              });
          } 
	  
        } else {
            //EDIT OBJECT
            if(data.constructor == Array) { 
                for(var i=0;i<columns.length;i++) {
                    var path = columns[i].split("/");
                    if (path.length == 1) {
                        tmpObject[columns[i]] = data[i];
                    } else {
                        var subObj = tmpObject[path[0]];
                        if (!subObj) {
                            subObj = oldObject[path[0]] || {};
                            tmpObject[path[0]] = subObj;
                        }
                        subObj[path[1]] = data[i];
                    }
                }  
            } else {
                for(var i=0;i<columns.length;i++) {
				   if(data[columns[i]]|| data[columns[i]]==0 || data[columns[i]]=="") { tmpObject[columns[i]]=data[columns[i]] }
                   else if(data[columns[i]] === undefined) { 
				       if ((oxMapping.module == "mail") && (columns[i] == "attachments_plain" || columns[i] == "attachments_html" || columns[i] == "attachments_html_noimage")) {
                           tmpObject[columns[i]] = data["attachments"] || null;
						   if(data["modified"] && data["modified"] == 1) {
                               tmpObject["blocked_images"]=true;
                           } else {
                            tmpObject["blocked_images"]=false;
						   }
                       } else if(columns[i]=="blocked_images") {
                       } else if (oxMapping.module == "mail" && columns[i] == "level") {
					   } else {
                           tmpObject[columns[i]] = null;
                       } 
				   } 
                }
				//UNREAD COUNTER
                if (   oxMapping.module == "mail"
                    && (data["unread"] || data["unread"]== 0))
                {
                    // was localUpdate
                    ox.api.folder.update({
                        local: true,
                        folder: tmpObject.folder_id || tmpObject.folder,
                        data: {
                            unread: data.unread
                        }
                    });
                } 
            }
			if(oldObject.last_modified==tmpObject.last_modified) {
			    for(var i=0;i<columns.length;i++) {
                    var path = columns[i].split("/");
                    if (path.length == 1) {
                        var oldO = oldObject, col = path[0];
                        var newvalue = tmpObject[col];
                    } else {
                        var oldO = oldObject[path[0]], col = path[1];
                        var newvalue = tmpObject[path[0]][col];
                    }
                    var value = oldO && oldO[col];
					//FIX FOR MAIL , Arrays should'nt be tested
					if(value !== undefined && newvalue !==undefined && value != newvalue && !itsupdate && typeof(value) != "object") {
						tmpObject.modified=true;
                        OXCache.modifiedObjects.push(tmpObject);
                        itsupdate=true;
					}
                    if (newvalue !== undefined) {
                        if (!oldO) oldO = oldObject[path[0]] = {};
                        oldO[col] = newvalue;
                    }
				}
			    if(!tmpObject.modified) {
				    oldObject.lastRequest=new Date().getTime();
				    if(oldObject.timestamp<timestamp) { oldObject["timestamp"]=timestamp; }
				    OXCache.cachedObjects.set(oldObject,oldObject);
				} else {
					//WORKAROUND FOR LEVEL PARAMETER IN MAIL
					if(oxMapping.module=="mail" && !tmpObject["level"] && (oldObject["level"] || oldObject["level"]==0)) {
						tmpObject["level"]=oldObject["level"];
					}
					tmpObject.lastRequest=new Date().getTime();
                    if(tmpObject.timestamp<timestamp) { tmpObject["timestamp"]=timestamp; }
					//ONLY CHECK CHANGES
					oxMapping.editObjectsInternal([tmpObject],tmpObject);
				}
            } else {
                //CHANGED OBJECT
				tmpObject.lastRequest=new Date().getTime();
				tmpObject.timestamp=timestamp;
                tmpObject.modified=true;
				OXCache.modifiedObjects.push(tmpObject);
                var tmp=OXCache.cachedObjects.set(tmpObject,tmpObject);
				if(tmp) {
                    OXCache.cachedObjectsTrash.set(tmp.key,tmp.data);
                }
                if(events) {
                    oxMapping.checkAddEditDeleteObject(tmpObject,executeCollection); 
                }
            }
        } 
        //TODO EXTRAS
    }  
}
OXAbstractMapping.getCollection = function(oxMapping) {
    return function(collection,first) {
        function search(myArray) {
            for(var colCount in myArray) {
                var tmpCollection=myArray[colCount].data;
                var same=true;
                //Check Criteria
                if(collection.criteria && tmpCollection.criteria) {
                    for(var criteriaCount in tmpCollection.criteria) {
                        if(collection.criteria[criteriaCount] != tmpCollection.criteria[criteriaCount]) {  
                            same=false; break;     
                        }
                    }
                    for(var criteriaCount in collection.criteria) {
                        if(tmpCollection.criteria[criteriaCount] != collection.criteria[criteriaCount]) {  
                            same=false; break;     
                        }
                    }
                } else if (!(!collection.criteria && !tmpCollection.criteria)) {
                    continue;
                }
                //Check Search
                if(collection.search && tmpCollection.search) {
                    for(var searchCount in tmpCollection.search) {
                        if(collection.search[searchCount] != tmpCollection.search[searchCount]) {  
                            same=false; break;     
                        }
                    }
                    for(var searchCount in collection.search) {
                        if(tmpCollection.search[searchCount] != collection.search[searchCount]) {  
                            same=false; break;     
                        }
                    }
                } else if (!(!collection.search && !tmpCollection.search)) {
                    continue;
                }
                
				if(!same) { continue; }
                
                //Check Order
                if(collection.order && tmpCollection.order) {
                    if(collection.order.length!=tmpCollection.order.length) {
                        continue;
                    }
                    var direction=0;
                    for(var i=0;i<collection.order.length;i++) {
                        var tmpObject=collection.order[i];
                        var tmpObject2=tmpCollection.order[i];
                        if(tmpObject.sort!=tmpObject2.sort) {
                            same=false;
                            break;
                        }
                        if(tmpObject.order!=tmpObject2.order) {
                            if(!direction) { direction=-1; }
                            if(direction != -1) { same=false; break; } 
                        } else {
                            if(!direction) { direction=1; }
                            if(direction != 1) { same=false; break; }
                        }
                        if(same!=true) { break; }
                    }
                    if(!same) { continue; }
                } else if (!(!collection.order && !tmpCollection.order)){
                    continue;
                }
                if(first) {
                    var tmp1=OXCache.cachedCollections.get(colCount);
                }
                return tmpCollection;
            }
        }
        return search(OXCache.cachedCollections.cache) || search(OXCache.cachedCollectionsTrash) 
    }
}
OXAbstractMapping.equalsCollection = function (oxMapping) {
	return function(collection1,collection2) {
	    if(collection1.criteria && collection2.criteria) {
            for(var criteriaCount in collection2) {
                if(collection1.criteria[criteriaCount] != collection2.criteria[criteriaCount]) {  return false;  }
            }
            for(var criteriaCount in collection1.criteria) {
                if(collection2.criteria[criteriaCount] != collection1.criteria[criteriaCount]) {  return false;  }
            }
        } else if (!(!collection1.criteria && !collection2.criteria)) { return false; }
        if(collection1.search && collection2.search) {
            for(var criteriaCount in collection2) {
                if(collection1.search[criteriaCount] != collection2.search[criteriaCount]) {  return false;  }
            }
            for(var criteriaCount in collection1.search) {
                if(collection2.search[criteriaCount] != collection1.search[criteriaCount]) {  return false;  }
            }
        } else if (!(!collection1.search && !collection2.search)) { return false; }
        
		if(collection1.order && collection2.order) {
            if(collection1.order.length!=collection2.order.length) { return false; }
            var direction=0;
            for(var i=0;i<collection1.order.length;i++) {
                var tmpObject=collection1.order[i];
                var tmpObject2=collection2.order[i];
                if(tmpObject.sort!=tmpObject2.sort) { return false; }
                if(tmpObject.order!=tmpObject2.order) {
                    if(!direction) { direction=-1; }
                    if(direction != -1) { return false; } 
                } else {
                    if(!direction) { direction=1; }
                    if(direction != 1) { return false; }
                }
            }
        }
		else if (!(!collection1.order && !collection2.order)){
            return false;
        }
        return true;	
	}
}
OXAbstractMapping.getSubCollections = function(oxMapping) {
    return function(collection,ignoreChecked) {
        function search(myArray) {
            var tmpArray=new Array();
            for(var colCount in myArray) {
                var tmpCollection=myArray[colCount].data;
                if(ignoreChecked && tmpCollection.check) { continue; }
                var same=true;
                if(collection.criteria && tmpCollection.criteria) {
                    //Check Criteria
                    for(var criteriaCount in tmpCollection.criteria) {
                        if(collection.criteria[criteriaCount] !== undefined && collection.criteria[criteriaCount] != tmpCollection.criteria[criteriaCount]) {  
                            same=false; break;     
                        }
                    }
                 }
				 if(collection.search && tmpCollection.search) {
                    //Check Criteria
                    for(var criteriaCount in tmpCollection.search) {
                        if(collection.search[criteriaCount] !== undefined && collection.search[criteriaCount] != tmpCollection.search[criteriaCount]) {  
                            same=false; break;     
                        }
                    }
                 }
                 if(!same) { continue; }
                tmpArray.push(tmpCollection);
            }
            return tmpArray;
        }
        return search(OXCache.cachedCollections.cache).concat(search(OXCache.cachedCollectionsTrash)); 
    }
}
OXAbstractMapping.getSubCollectionsfromObject = function(oxMapping) {
    return function(object,ignoreChecked) {
        function search(myArray) {
            var tmpArray=new Array();
            for(var colCount in myArray) {
                var tmpCollection=myArray[colCount].data;
                if(ignoreChecked && tmpCollection.check) { continue; }
                var same=true;
                if(tmpCollection.criteria) {
                   for(var criteriaCount in tmpCollection.criteria) {
                        if(same!=true) { break; }
                        switch(criteriaCount) {
                            default:
                                if(object[criteriaCount] !== undefined && object[criteriaCount] != tmpCollection.criteria[criteriaCount]) {  
                                    same=false;
                                    break;     
                                }
                                break;
                        }
                    }
                 }
                 if(!same) { continue; }
                tmpArray.push(tmpCollection);
            }
            return tmpArray;
        }
        return search(OXCache.cachedCollections.cache).concat(search(OXCache.cachedCollectionsTrash)); 
    }
}

OXAbstractMapping.convertColumns = function(oxMapping) {
    return function(columns) {
   	    var retval = { columns: [] };
        for(var i = 0; i < columns.length; i++) {
            var col = trimStr(columns[i]);
            var tmpColumn=oxMapping.stringmapping[col];
            if(!tmpColumn) {
                var path = col.split("/");
                // TODO: arbitrary path lengths
                if (path.length == 2) {
                    if (!(path[0] in retval)) retval[path[0]] = [];
                    retval[path[0]].push(path[1]);
                    continue;
                }
                if (oxMapping.alternative) {
                    tmpColumn=oxMapping.alternative[col];
                }
                if(typeof tmpColumn == "function") tmpColumn = false;
			}
			if(!tmpColumn && tmpColumn != false) { 
			    if(debug) { alert("Column "+columns[i]+" not found in Mapping")}
			}
            retval.columns.push(tmpColumn);
		}
		return retval;
    }
}
OXAbstractMapping.setTag = function(oxMapping) {
	return function(tag,ids) {
		var multipleObject = [];
		for(var i = 0; i < ids.length; i++) {
            multipleObject.push({ 
                 action    : "update", 
                 module    : ids[i].module, 
                 timestamp : ids[i].timestamp || 0, 
                 id        : ids[i].id, 
                 folder    : ids[i].folder_id || ids[i].folder,
                 data      : { color_label : tag } 
            });
			if(ids[i].recurrence_position && ids[i].recurrence_position > 0) {
                multipleObject[i] = ids[i].recurrence_position;
            }
        }
        json.put(AjaxRoot + "/multiple?session=" + session + "&continue=true",multipleObject,null,
            function(callback){
            	var timestamp = 0;
                for(var i=0;i<callback.length;i++) {
                    if(callback[i].error) {
						newServerError(callback[i]);
                        return;
                    } else if (callback[i].timestamp && callback[i].timestamp > timestamp) {
                            timestamp = callback[i].timestamp;
                    }
                }
                oxMapping.editObjectsInternal(ids,{ color_label : tag , timestamp : timestamp });                       
            }
        );
 	}
}




  /////////////////////////////////////
 //   OX Mapping Implementation     //
/////////////////////////////////////
function OXTaskMapping() {}
OXTaskMapping.mandatoryfields=["folder_id","id","last_modified", "created_by", "creation_date"];
OXTaskMapping.idmapping= {
    "1": "id", "2": "created_by", "3": "modified_by", "4": "creation_date", "5": "last_modified", 
    "20": "folder_id", "100": "categories", "101": "private_flag", "104" : "number_of_attachments", "200": "title", "201": "start_date", 
    "202": "end_date", "203": "note", "204": "alarm",   
    "209": "recurrence_type", "212": "days", "213": "days_in_month", 
    "214": "month", "215": "internal", "216": "until",  "220": "participants", "221" : "users",
    "300": "status", "301": "percent_completed", "302": "actual_costs", 
    "303": "actual_duration", "305": "billing_information", 
    "307": "target_costs", "308": "target_duration", "309": "priority", 
    "312": "currency", "313": "trip_meter", "314": "companies", 
    "315": "date_completed", "102": "color_label"
} 
//"207": "recurrence_position", 208": "recurrence_date_position","217": "notification", "306": "project_id"
// ???
// "310": "duration_type", "311": "type_of_costs"

OXTaskMapping.stringmapping=switchStringObject(OXTaskMapping.idmapping);
OXTaskMapping.module="tasks";
OXTaskMapping.createKeyFromData = function(myArray,timestamp) {
     var tmp=new OXTaskObjectCache();
     tmp.module = OXTaskMapping.module;
     tmp.folder_id=myArray[0];
     tmp.id=myArray[1];
     tmp.created_by = myArray[3];
	 if(timestamp) { tmp.timestamp = timestamp }
     return tmp;     
}
OXTaskMapping.createKeyFromObject = function(myObject) {
     var tmp=new OXTaskObjectCache();
     tmp.module = OXTaskMapping.module;
     tmp.folder_id=myObject["folder_id"];
     tmp.id=myObject["id"];
     tmp.created_by = myObject.created_by;
     return tmp;     
}

OXTaskMapping.objectConstructor=OXTaskObjectCache;
OXTaskMapping.get = OXAbstractMapping.get(OXTaskMapping);
OXTaskMapping.execute = OXAbstractMapping.execute(OXTaskMapping);
OXTaskMapping.buildResponse = OXAbstractMapping.buildResponse(OXTaskMapping);
OXTaskMapping.criteriaRequest= OXAbstractMapping.criteriaRequest(OXTaskMapping);
OXTaskMapping.listRequest= OXAbstractMapping.listRequest(OXTaskMapping);
OXTaskMapping.addCriteriaNotInCache = OXAbstractMapping.addCriteriaNotInCache(OXTaskMapping);
OXTaskMapping.addCriteriaExpired = OXAbstractMapping.addCriteriaExpired(OXTaskMapping);
OXTaskMapping.addCriteriaMissingColumn = OXAbstractMapping.addCriteriaMissingColumn(OXTaskMapping);
OXTaskMapping.addListNotInCache = OXAbstractMapping.addListNotInCache(OXTaskMapping);
OXTaskMapping.addListExpired = OXAbstractMapping.addListExpired(OXTaskMapping);
OXTaskMapping.addListMissingColumn = OXAbstractMapping.addListMissingColumn(OXTaskMapping);
OXTaskMapping.request = OXAbstractMapping.request(OXTaskMapping);
OXTaskMapping.handleResponse = OXAbstractMapping.handleResponse(OXTaskMapping);
OXTaskMapping.handleCriteriaNotInCache = OXAbstractMapping.handleCriteriaNotInCache(OXTaskMapping);
OXTaskMapping.handleCriteriaExpired = OXAbstractMapping.handleCriteriaExpired(OXTaskMapping);
OXTaskMapping.handleCriteriaMissingColumn = OXAbstractMapping.handleCriteriaMissingColumn(OXTaskMapping);
OXTaskMapping.handleCriteria=OXAbstractMapping.handleCriteria(OXTaskMapping);
OXTaskMapping.handleListNotInCache = OXAbstractMapping.handleListNotInCache(OXTaskMapping);
OXTaskMapping.handleListExpired = OXAbstractMapping.handleListExpired(OXTaskMapping);
OXTaskMapping.handleListMissingColumn = OXAbstractMapping.handleListMissingColumn(OXTaskMapping);
OXTaskMapping.handleList = OXAbstractMapping.handleList(OXTaskMapping);
OXTaskMapping.getCollection= OXAbstractMapping.getCollection(OXTaskMapping);
OXTaskMapping.convertColumns = OXAbstractMapping.convertColumns(OXTaskMapping);
OXTaskMapping.createCacheObject = OXAbstractMapping.createCacheObject(OXTaskMapping);
OXTaskMapping.checkAddCriteria = OXAbstractMapping.checkAddCriteria(OXTaskMapping);
OXTaskMapping.checkAddEditDeleteObject= OXAbstractMapping.checkAddEditDeleteObject(OXTaskMapping);
OXTaskMapping.getSubCollectionsfromObject = OXAbstractMapping.getSubCollectionsfromObject(OXTaskMapping);
OXTaskMapping.getSubCollections = OXAbstractMapping.getSubCollections(OXTaskMapping);
OXTaskMapping.equalsCollection = OXAbstractMapping.equalsCollection(OXTaskMapping);
OXTaskMapping.errorHandler = OXCache.errorHandler;
OXTaskMapping.deleteObjectsInternal = function(objects) {
    var tmpFn=OXCache.join.add();
    var oxMapping=OXTaskMapping;

    // Calling all "before" callbacks.
    for(var i in OXCache.modifiedCallbacks) {
        var mc = OXCache.modifiedCallbacks[i];
        if (mc.module == oxMapping.module && mc.before) mc.before();
    }
    
    var tmpCollect=[];
    for(var mail=0;mail<objects.length;mail++) {
        var collections=oxMapping.getSubCollectionsfromObject(objects[mail],true);
        for(var count=0;count<collections.length;count++) {
            if(collections[count].check) { continue; } 
            var index=collections[count].map_objects.get(objects[mail]);
            if(index) {
                 collections[count].modified=true;
                 if(!collections[count].tmpIndexes) { collections[count].tmpIndexes=[]; }
                 collections[count].tmpIndexes.push(index);
                 tmpCollect.push(collections[count]);
                 collections[count].modifiedtmp=true;
             }               
        }
    }
    
    for(var count=0;count<tmpCollect.length;count++) {
        if(tmpCollect[count].modifiedtmp) {
            var indexes=tmpCollect[count].tmpIndexes;
            indexes.sort(function(a,b) { return a-b; });
            for(var i=indexes.length-1;i>=0;i--) {
                tmpCollect[count].objects.splice(indexes[i]-1,1);
            }
            delete tmpCollect[count].tmpIndexes;
            tmpCollect[count].map_objects=new LRUKeyList();
            for(var index=0;index<tmpCollect[count].objects.length;index++) { 
                tmpCollect[count].map_objects.set(tmpCollect[count].objects[index],index+1) 
            }
            tmpCollect[count].modifiedtmp=false;
        }
    }
    tmpFn();
}

OXTaskMapping.deleteObjects = function(objects) {
    var oxMapping=OXTaskMapping;
    for(var i=0;i<objects.length;i++) {
        var tmpTime=objects[i]["timestamp"]; 
        if (!tmpTime) {
            var tmpObject = OXCache.cachedObjects.get(objects[i]);
            if(tmpObject) {
                tmpTime=tmpObject.timestamp;
            }
        }
		var request = new Array();
        for (var i in objects) {
            request.push({
                module: "tasks", 
                action: "delete",
                folder: objects[i]["folder_id"],
                timestamp: objects[i]["timestamp"],
                data: { folder : objects[i]["folder_id"] , id : objects[i]["id"] }
            });
        }
    }
    json.put(AjaxRoot + "/multiple?session=" + session + "&continue=true",
        request, null, function(reply) {
            var tmpFn=OXCache.join.add();
            var error=new Array();
            for(var i=0;i<reply.length;i++) {
                if(reply[i].error) {
                    newServerError(reply[i]);
                    error.push(i);   
                }  
            }
            if(!error.length) { oxMapping.deleteObjectsInternal(objects); }
            tmpFn();        
        }
    );
}
OXTaskMapping.editObjectsInternal = function(oldObjects,changes,checkExist) {
    var oxMapping=OXTaskMapping;
    var tmpFn=OXCache.join.add();
    for(var myObject in oldObjects) {
        var tmpKeyOld=oxMapping.createKeyFromObject(oldObjects[myObject]);
        var collections=oxMapping.getSubCollectionsfromObject(oldObjects[myObject],true);
        //TODO Check Changes in Collection
        for(var i=0;i<collections.length;i++) {
            if(!collections[i].check) {
                collections[i].check=true;
            } 
        }
        for(var i in OXCache.modifiedCallbacks) {
            if(OXCache.modifiedCallbacks[i].module == oxMapping.module && !(OXCache.modifiedCallbacks[i].collection.criteria)) {
                for(var count=0;count<OXCache.modifiedCallbacks[i].collection.objects.length;count++) {
                    if(tmpKeyOld.equals(OXCache.modifiedCallbacks[i].collection.objects[count])) {
                        var mc = OXCache.modifiedCallbacks[i];
                        if (!mc.modified && mc.before) mc.before();
                        mc.modified=true;
                    }
                }
            }
        }
        var tmpObjectOld=OXCache.cachedObjects.get(tmpKeyOld);
        if(tmpObjectOld) {
           if(changes) {
              for(var i in changes) {
                  if(!checkExist || tmpObjectOld[i]!== undefined) {
                      if(i != "notification") {
                          tmpObjectOld[i]=changes[i];
                      }
                  }
              }
           }
       }
       OXCache.cachedObjects.set(tmpKeyOld,tmpObjectOld);    
    }  
    //TODO CHECK CHANGE FOLDER
    tmpFn();
}

OXTaskMapping.editObjects = function(oldObjects,changes) {
    setTimeout(function() {
       var tmpFn=OXCache.join.add();
       OXTaskMapping.editObjectsInternal(oldObjects,changes);
       tmpFn(); 
    },0);
}
OXTaskMapping.createObject = function(newObject) {
    setTimeout(function() {
        var tmpFn=OXCache.join.add();
        OXTaskMapping.createObjectsInternal([newObject]);
        tmpFn();
    },0);
}
OXTaskMapping.createObjectsInternal = function(newObjects) {
    setTimeout(function() {
        var oxMapping=OXTaskMapping;

        // Calling all "before" callbacks.
        for(var i in OXCache.modifiedCallbacks) {
            var mc = OXCache.modifiedCallbacks[i];
            if (mc.module == oxMapping.module && mc.before) mc.before();
        }
        
        var tmpFn=OXCache.join.add();
        for(var object in newObjects) {
            var collections=oxMapping.getSubCollectionsfromObject(newObjects[object],true);
            for(var i=0;i<collections.length;i++) {
                if(!collections[i].check) {
                    collections[i].check=true;
                }
            }
        }
        tmpFn();
    },0);
}
OXTaskMapping.setTag = OXAbstractMapping.setTag(OXTaskMapping);
OXCache.setMapping("tasks",OXTaskMapping);




function OXCalendarMapping() {}
OXCalendarMapping.mandatoryfields=["folder_id", "id", "recurrence_position",
                                   "last_modified", "created_by"];
OXCalendarMapping.idmapping= { 
    "1": "id", "2": "created_by", "3": "modified_by", "4": "creation_date", "5":"last_modified",
    "20": "folder_id", "100": "categories", "101": "private_flag", "102":"color_label", 
    "104": "number_of_attachments", "200": "title", 
    "201": "start_date", "202": "end_date", "203": "note", "204": "alarm",  
    "207": "recurrence_position", "208": "recurrence_date_position", "209": "recurrence_type",
    "212": "days", "213": "days_in_month", "214": "month", "215": "interval", "216": "until",
    "220": "participants", "221": "users", "400": "location",
    "401": "full_time", "402": "shown_as"
}
//"217": notification"
OXCalendarMapping.module="calendar";
OXCalendarMapping.createKeyFromData = function(myArray,timestamp) {
     var tmp=new OXCalendarObjectCache();
     tmp.module = OXCalendarMapping.module;
     tmp.folder_id=myArray[0];
     tmp.id=myArray[1];
     tmp.recurrence_position=myArray[2] || 0;
     tmp.created_by = myArray[4];
	  if(timestamp) { tmp.timestamp = timestamp }
     return tmp;     
}
OXCalendarMapping.createKeyFromObject = function(myObject) {
     var tmp=new OXCalendarObjectCache();
     tmp.module = OXCalendarMapping.module;
     tmp.folder_id=myObject["folder_id"];
     tmp.id=myObject["id"];
	 tmp.recurrence_position=myObject.recurrence_position;
     tmp.created_by = myObject.created_by;
     return tmp;     
}

OXCalendarMapping.objectConstructor=OXCalendarObjectCache;
OXCalendarMapping.stringmapping=switchStringObject(OXCalendarMapping.idmapping);
OXCalendarMapping.get = OXAbstractMapping.get(OXCalendarMapping);
OXCalendarMapping.execute = OXAbstractMapping.execute(OXCalendarMapping);
OXCalendarMapping.buildResponse = OXAbstractMapping.buildResponse(OXCalendarMapping);
OXCalendarMapping.criteriaRequest= OXAbstractMapping.criteriaRequest(OXCalendarMapping);
OXCalendarMapping.listRequest= OXAbstractMapping.listRequest(OXCalendarMapping);
OXCalendarMapping.addCriteriaNotInCache = OXAbstractMapping.addCriteriaNotInCache(OXCalendarMapping);
OXCalendarMapping.addCriteriaExpired = OXAbstractMapping.addCriteriaExpired(OXCalendarMapping);
OXCalendarMapping.addCriteriaMissingColumn = OXAbstractMapping.addCriteriaMissingColumn(OXCalendarMapping);
OXCalendarMapping.addListNotInCache = OXAbstractMapping.addListNotInCache(OXCalendarMapping);
OXCalendarMapping.addListExpired = OXAbstractMapping.addListExpired(OXCalendarMapping);
OXCalendarMapping.addListMissingColumn = OXAbstractMapping.addListMissingColumn(OXCalendarMapping);
OXCalendarMapping.request = OXAbstractMapping.request(OXCalendarMapping);
OXCalendarMapping.handleResponse = OXAbstractMapping.handleResponse(OXCalendarMapping);
OXCalendarMapping.handleCriteriaNotInCache = OXAbstractMapping.handleCriteriaNotInCache(OXCalendarMapping);
OXCalendarMapping.handleCriteriaExpired = OXAbstractMapping.handleCriteriaExpired(OXCalendarMapping);
OXCalendarMapping.handleCriteriaMissingColumn = OXAbstractMapping.handleCriteriaMissingColumn(OXCalendarMapping);
OXCalendarMapping.handleCriteria=OXAbstractMapping.handleCriteria(OXCalendarMapping);
OXCalendarMapping.handleListNotInCache = OXAbstractMapping.handleListNotInCache(OXCalendarMapping);
OXCalendarMapping.handleListExpired = OXAbstractMapping.handleListExpired(OXCalendarMapping);
OXCalendarMapping.handleListMissingColumn = OXAbstractMapping.handleListMissingColumn(OXCalendarMapping);
OXCalendarMapping.handleList = OXAbstractMapping.handleList(OXCalendarMapping);
OXCalendarMapping.getCollection= OXAbstractMapping.getCollection(OXCalendarMapping);
OXCalendarMapping.convertColumns = OXAbstractMapping.convertColumns(OXCalendarMapping);
OXCalendarMapping.createCacheObject = OXAbstractMapping.createCacheObject(OXCalendarMapping);
OXCalendarMapping.checkAddCriteria = OXAbstractMapping.checkAddCriteria(OXCalendarMapping);
OXCalendarMapping.checkAddEditDeleteObject= OXAbstractMapping.checkAddEditDeleteObject(OXCalendarMapping);
OXCalendarMapping.getSubCollectionsfromObject = OXAbstractMapping.getSubCollectionsfromObject(OXCalendarMapping);
OXCalendarMapping.getSubCollections = OXAbstractMapping.getSubCollections(OXCalendarMapping);
OXCalendarMapping.equalsCollection = OXAbstractMapping.equalsCollection(OXCalendarMapping);
OXCalendarMapping.errorHandler = OXCache.errorHandler;

OXCalendarMapping.editObjectsInternal = function(oldObjects,changes,checkExist) {
    var oxMapping=OXCalendarMapping;
    var tmpFn=OXCache.join.add();
    for(var myObject in oldObjects) {
        var tmpKeyOld=oxMapping.createKeyFromObject(oldObjects[myObject]);
        var collections=oxMapping.getSubCollectionsfromObject(oldObjects[myObject],true);
        //TODO Check Changes in Collection
        for(var i=0;i<collections.length;i++) {
            if(!collections[i].check) {
                collections[i].check=true;
            } 
        }
        for(var i in OXCache.modifiedCallbacks) {
            if(OXCache.modifiedCallbacks[i].module == oxMapping.module && !(OXCache.modifiedCallbacks[i].collection.criteria)) {
                for(var count=0;count<OXCache.modifiedCallbacks[i].collection.objects.length;count++) {
                    if(tmpKeyOld.equals(OXCache.modifiedCallbacks[i].collection.objects[count])) {
                        var mc = OXCache.modifiedCallbacks[i];
                        if (!mc.modified && mc.before) mc.before();
                        mc.modified=true;
                    }
                }
            }
        }
        var tmpObjectOld=OXCache.cachedObjects.get(tmpKeyOld);
        if(tmpObjectOld) {
           if(changes) {
              for(var i in changes) {
                  if(!checkExist || tmpObjectOld[i]!== undefined) {
                      if(i != "notification") {
                          tmpObjectOld[i]=changes[i];
                      }
                  }
              }
           }
       }
       OXCache.cachedObjects.set(tmpKeyOld,tmpObjectOld);    
    }  
    //TODO CHECK CHANGE FOLDER
    tmpFn();
}

OXCache.setMapping("calendar", OXCalendarMapping);

function OXContactMapping() {}
OXContactMapping.mandatoryfields = ["folder_id", "id", "last_modified",
                                    "created_by"];
OXContactMapping.idmapping= { 
    "1": "id", "2": "created_by", "3": "modified_by", "4": "creation_date", "5": "last_modified", 
    "20": "folder_id", "100": "categories", "101": "private_flag", "500": "display_name", "501": "first_name",
    "502": "last_name", "503": "second_name", "504": "suffix", "505": "title", "506": "street_home",
    "507": "postal_code_home", "508": "city_home", "509": "state_home", "510": "country_home", 
    "511": "birthday", "512": "marital_status", "513": "number_of_children", "514": "profession", 
    "515": "nickname", "516": "spouse_name", "517": "anniversary", "518": "note", "519": "department", 
    "520": "position", "521": "employee_type", "522": "room_number", "523": "street_business", 
    "525": "postal_code_business", "526": "city_business", "527": "state_business", "528": "country_business",
    "529": "number_of_employees", "530": "sales_volume", "531": "tax_id", "532": "commercial_register",
    "533": "branches", "534": "business_category", "535": "info", "536": "manager_name", 
    "537": "assistant_name", "538": "street_other", "539": "city_other", "540": "postal_code_other", 
    "541": "country_other", "542": "telephone_business1", "543": "telephone_business2", "544": "fax_business",
    "545": "telephone_callback", "546": "telephone_car", "547": "telephone_company", "548": "telephone_home1",
    "549": "telephone_home2", "550": "fax_home", "551": "cellular_telephone1", "552": "cellular_telephone2",
    "553": "telephone_other", "554": "fax_other", "555": "email1", "556": "email2", "557": "email3",
    "558": "url", "559": "telephone_isdn", "560": "telephone_pager", "561": "telephone_primary",
    "562": "telephone_radio", "563": "telephone_telex", "564": "telephone_ttytdd",
    "565": "instant_messenger1", "566": "instant_messenger2", "567": "telephone_ip",
    "568": "telephone_assistant", "569": "company", /* "570": "image1",*/ "571": "userfield01",
    "572": "userfield02", "573": "userfield03", "574": "userfield04", "575": "userfield05",
    "576": "userfield06", "577": "userfield07", "578": "userfield08", "579": "userfield09",
    "580": "userfield10", "581": "userfield11", "582": "userfield12", "583": "userfield13",
    "584": "userfield14", "585": "userfield15", "586": "userfield16", "587": "userfield17",
    "588": "userfield18", "589": "userfield19", "590": "userfield20",
    "592": "distribution_list", "594": "number_of_distribution_list",
    "596": "contains_image1", "597": "image_last_modified", "598": "state_other",
    "599": "file_as", "104": "number_of_attachments", "601": "image1_content_type", "602": "mark_as_distributionlist",
    "605": "default_address", "102": "color_label", "524": "internal_userid",
    "606": "image1_url"
}
OXContactMapping.stringmapping=switchStringObject(OXContactMapping.idmapping);
OXContactMapping.module="contacts";
OXContactMapping.createKeyFromData = function(myArray,timestamp) {
     var tmp=new OXContactObjectCache();
     tmp.module = OXContactMapping.module;
     tmp.folder_id=myArray[0];
     tmp.id=myArray[1];
     tmp.created_by = myArray[3];
     if(timestamp) { tmp.timestamp = timestamp }
     return tmp;     
}
OXContactMapping.createKeyFromObject = function(myObject) {
     var tmp=new OXContactObjectCache();
     tmp.module = OXContactMapping.module;
     tmp.folder_id=myObject["folder_id"];
     tmp.id=myObject["id"];
     tmp.created_by = myObject.created_by;
     return tmp;     
}


OXContactMapping.objectConstructor=OXContactObjectCache;
OXContactMapping.get = OXAbstractMapping.get(OXContactMapping);
OXContactMapping.execute = OXAbstractMapping.execute(OXContactMapping);
OXContactMapping.buildResponse = OXAbstractMapping.buildResponse(OXContactMapping);
OXContactMapping.criteriaRequest= OXAbstractMapping.criteriaRequest(OXContactMapping);
OXContactMapping.listRequest= OXAbstractMapping.listRequest(OXContactMapping);
OXContactMapping.addCriteriaNotInCache = OXAbstractMapping.addCriteriaNotInCache(OXContactMapping);
OXContactMapping.addCriteriaExpired = OXAbstractMapping.addCriteriaExpired(OXContactMapping);
OXContactMapping.addCriteriaMissingColumn = OXAbstractMapping.addCriteriaMissingColumn(OXContactMapping);
OXContactMapping.addListNotInCache = OXAbstractMapping.addListNotInCache(OXContactMapping);
OXContactMapping.addListExpired = OXAbstractMapping.addListExpired(OXContactMapping);
OXContactMapping.addListMissingColumn = OXAbstractMapping.addListMissingColumn(OXContactMapping);
OXContactMapping.request = OXAbstractMapping.request(OXContactMapping);
OXContactMapping.handleResponse = OXAbstractMapping.handleResponse(OXContactMapping);
OXContactMapping.handleCriteriaNotInCache = OXAbstractMapping.handleCriteriaNotInCache(OXContactMapping);
OXContactMapping.handleCriteriaExpired = OXAbstractMapping.handleCriteriaExpired(OXContactMapping);
OXContactMapping.handleCriteriaMissingColumn = OXAbstractMapping.handleCriteriaMissingColumn(OXContactMapping);
OXContactMapping.handleCriteria=OXAbstractMapping.handleCriteria(OXContactMapping);
OXContactMapping.handleListNotInCache = OXAbstractMapping.handleListNotInCache(OXContactMapping);
OXContactMapping.handleListExpired = OXAbstractMapping.handleListExpired(OXContactMapping);
OXContactMapping.handleListMissingColumn = OXAbstractMapping.handleListMissingColumn(OXContactMapping);
OXContactMapping.handleList = OXAbstractMapping.handleList(OXContactMapping);
OXContactMapping.getCollection= OXAbstractMapping.getCollection(OXContactMapping);
OXContactMapping.convertColumns = OXAbstractMapping.convertColumns(OXContactMapping);
OXContactMapping.createCacheObject = OXAbstractMapping.createCacheObject(OXContactMapping);
OXContactMapping.checkAddCriteria = OXAbstractMapping.checkAddCriteria(OXContactMapping);
OXContactMapping.checkAddEditDeleteObject= OXAbstractMapping.checkAddEditDeleteObject(OXContactMapping);
OXContactMapping.getSubCollectionsfromObject = OXAbstractMapping.getSubCollectionsfromObject(OXContactMapping);
OXContactMapping.getSubCollections = OXAbstractMapping.getSubCollections(OXContactMapping);
OXContactMapping.equalsCollection = OXAbstractMapping.equalsCollection(OXContactMapping);
OXContactMapping.errorHandler = OXCache.errorHandler;

OXContactMapping.deleteObjectsInternal = function(objects) {
    var tmpFn=OXCache.join.add();
    var oxMapping=OXContactMapping;

    // Calling all "before" callbacks.
    for(var i in OXCache.modifiedCallbacks) {
        var mc = OXCache.modifiedCallbacks[i];
        if (mc.module == oxMapping.module && mc.before) mc.before();
    }
    
    var tmpCollect=[];
    for(var mail=0;mail<objects.length;mail++) {
        var collections=oxMapping.getSubCollectionsfromObject(objects[mail],true);
        for(var count=0;count<collections.length;count++) {
            if(collections[count].check) { continue; } 
            var index=collections[count].map_objects.get(objects[mail]);
            if(index) {
                 collections[count].modified=true;
                 if(!collections[count].tmpIndexes) { collections[count].tmpIndexes=[]; }
                 collections[count].tmpIndexes.push(index);
                 tmpCollect.push(collections[count]);
                 collections[count].modifiedtmp=true;
             }               
        }
    }
    for(var count=0;count<tmpCollect.length;count++) {
        if(tmpCollect[count].modifiedtmp) {
                var indexes=tmpCollect[count].tmpIndexes;
                indexes.sort(function(a,b) { return a-b; });
                for(var i=indexes.length-1;i>=0;i--) {
                    tmpCollect[count].objects.splice(indexes[i]-1,1);
                }
                delete tmpCollect[count].tmpIndexes;
            tmpCollect[count].map_objects=new LRUKeyList();
            for(var index=0;index<tmpCollect[count].objects.length;index++) { 
                tmpCollect[count].map_objects.set(tmpCollect[count].objects[index],index+1) 
            }
            tmpCollect[count].modifiedtmp=false;
        }
    }
    tmpFn();
}

OXContactMapping.deleteObjects = function(objects) {
    var oxMapping=OXContactMapping;
    var request = new Array();
    for (var i in objects) {
		var tmpTime=objects[i]["timestamp"]; 
        if (!tmpTime) {
            var tmpObject = OXCache.cachedObjects.get(objects[i]);
            if(tmpObject) {
                tmpTime=tmpObject.timestamp;
            }
        }
        request.push({
            module: oxMapping.module, 
            action: "delete",
            folder: objects[i]["folder_id"],
            timestamp: tmpTime ,
            data: { folder : objects[i]["folder_id"] , id : objects[i]["id"] }
        });
    }
    json.put(AjaxRoot + "/multiple?session=" + session + "&continue=true",
        request, null, function(reply) {
            var tmpFn=OXCache.join.add();
            var error=new Array();
            for(var i=0;i<reply.length;i++) {
                if(reply[i].error) {
                    newServerError(reply[i]);
                    error.push(i);   
                }  
            }
            if(!error.length) {
                oxMapping.deleteObjectsInternal(objects);
            }
            tmpFn();            
        }
    );
}
OXContactMapping.editObjects = function(oldObjects,changes) {
    setTimeout(function() {
       var tmpFn=OXCache.join.add();
       OXContactMapping.editObjectsInternal(oldObjects,changes);
       tmpFn(); 
    },0);
}
OXContactMapping.editObjectsInternal = function(oldObjects,changes,checkExist) {
    var oxMapping=OXContactMapping;
    var tmpFn=OXCache.join.add();
    for(var myObject in oldObjects) {
        var tmpKeyOld=oxMapping.createKeyFromObject(oldObjects[myObject]);
        var collections=oxMapping.getSubCollectionsfromObject(oldObjects[myObject],true);
        //TODO Check Changes in Collection
        for(var i=0;i<collections.length;i++) {
            if(!collections[i].check) {
                collections[i].check=true;
            } 
        }
        for(var i in OXCache.modifiedCallbacks) {
            if(OXCache.modifiedCallbacks[i].module == oxMapping.module && !(OXCache.modifiedCallbacks[i].collection.criteria)) {
                for(var count=0;count<OXCache.modifiedCallbacks[i].collection.objects.length;count++) {
                    if(tmpKeyOld.equals(OXCache.modifiedCallbacks[i].collection.objects[count])) {
                        var mc = OXCache.modifiedCallbacks[i];
                        if (!mc.modified && mc.before) mc.before();
                        mc.modified=true;    
                    }
                }
            }
        }
        var tmpObjectOld=OXCache.cachedObjects.get(tmpKeyOld);
        if(tmpObjectOld) {
           if(changes) {
              for(var i in changes) {
                  if(!checkExist || tmpObjectOld[i]!== undefined) {
                      if(i != "notification") {
                          tmpObjectOld[i]=changes[i];
                      }
                  }
              }
           }
       }
       OXCache.cachedObjects.set(tmpKeyOld,tmpObjectOld);    
    }  
    //TODO CHECK CHANGE FOLDER
    tmpFn();
}

OXContactMapping.createObject = function(newObject) {
    setTimeout(function() {
        var tmpFn=OXCache.join.add();
        OXContactMapping.createObjectsInternal([newObject]);
        tmpFn();
    },0);
}
OXContactMapping.createObjectsInternal = function(newObjects) {
    setTimeout(function() {
        var oxMapping=OXContactMapping;

        // Calling all "before" callbacks.
        for(var i in OXCache.modifiedCallbacks) {
            var mc = OXCache.modifiedCallbacks[i];
            if (mc.module == oxMapping.module && mc.before) mc.before();
        }
        
        var tmpFn=OXCache.join.add();
        for(var object in newObjects) {
            var collections=oxMapping.getSubCollectionsfromObject(newObjects[object],true);
            for(var i=0;i<collections.length;i++) {
                if(!collections[i].check) {
                    collections[i].check=true;
                }
            }
        }
        tmpFn();
    },0);
}
OXContactMapping.setTag = OXAbstractMapping.setTag(OXContactMapping);

OXCache.setMapping("contacts",OXContactMapping);

function OXMailMapping() {} 
OXMailMapping.mandatoryfields=["id","folder_id"];
OXMailMapping.idmapping = {
    "600": "id", "601": "folder_id", "602": "attachment", "603": "from", "604": "to", "605": "cc", 
    "606": "bcc", "607": "subject", "608": "size", "609": "sent_date", "610": "received_date", 
    "611": "flags", "612": "level", "614": "priority", "102": "color_label", "652": "account_name"  
}
OXMailMapping.alternative = {
	"user" : emptyFunction, "attachments": emptyFunction,
	"nested_msgs": emptyFunction, "attachments_html": function() {
        return configGetKey("mail.inlineattachments") &&
            configGetKey("modules.mail.allowhtmlimages");
    }, "attachments_html_noimage": function() {
        return configGetKey("mail.inlineattachments") &&
            !configGetKey("modules.mail.allowhtmlimages");
    }, "attachments_plain": function() {
        return !configGetKey("mail.inlineattachments");
    }, "blocked_images": emptyFunction, "mailtext": "-1",
    "disp_notification_to": "613", "flags_sort": "651", "headers": emptyFunction 
};
OXMailMapping.GETHTML = [
    "id","folder_id","attachment","attachments_html","from","to","cc","bcc","subject","size","sent_date","received_date","flags",
    "priority","color_label","account_name","user","nested_msgs","disp_notification_to",
    "flags_sort", "headers" 
];
OXMailMapping.GETPLAIN = [
    "id","folder_id","attachment","attachments_plain","from","to","cc","bcc","subject","size","sent_date","received_date","flags",
    "priority","color_label","account_name","user","nested_msgs","disp_notification_to",
    "flags_sort", "headers" 
];
OXMailMapping.GETHTMLNOIMAGE = [
    "id","folder_id","attachment","attachments_html_noimage","blocked_images","from","to","cc","bcc","subject","size","sent_date","received_date","flags",
    "priority","color_label","account_name","user","nested_msgs","disp_notification_to",
    "flags_sort", "headers"  
];

OXMailMapping.flagmapping = { "seen" : { flag : 32 , bool : true} , "unseen" : { flag : 32 , bool : false} ,
                            "answer" : { flag : 1 , bool : true} , "answered" : { flag : 1 , bool : false},
                            "delete" : { flag : 2 , bool : true} , "undelete" : { flag : 2 , bool : false},
                            "spam" : { flag : 128 , bool : true} , "ham" : { flag : 128 , bool : false} };

OXMailMapping.module="mail";
OXMailMapping.createKeyFromData = function(myArray,timestamp) {
     var tmp=new OXMailObjectCache();
     tmp.module = OXMailMapping.module;
     tmp.folder_id=myArray[1];
     tmp.id=myArray[0];
     return tmp;     
}
OXMailMapping.createKeyFromObject = function(myObject) {
     var tmp=new OXMailObjectCache();
     tmp.module = OXMailMapping.module;
     tmp.folder_id=myObject["folder_id"];
     tmp.id=myObject["id"];
     return tmp;     
}


OXMailMapping.stringmapping=switchStringObject(OXMailMapping.idmapping);
OXMailMapping.objectConstructor=OXMailObjectCache;
OXMailMapping.stringmapping=switchStringObject(OXMailMapping.idmapping);
OXMailMapping.get = OXAbstractMapping.get(OXMailMapping);
OXMailMapping.execute = OXAbstractMapping.execute(OXMailMapping);
OXMailMapping.buildResponse = OXAbstractMapping.buildResponse(OXMailMapping);
OXMailMapping.criteriaRequest= OXAbstractMapping.criteriaRequest(OXMailMapping);
OXMailMapping.listRequest= OXAbstractMapping.listRequest(OXMailMapping);
OXMailMapping.addCriteriaNotInCache = OXAbstractMapping.addCriteriaNotInCache(OXMailMapping);
OXMailMapping.addCriteriaExpired = OXAbstractMapping.addCriteriaExpired(OXMailMapping);
OXMailMapping.addCriteriaMissingColumn = OXAbstractMapping.addCriteriaMissingColumn(OXMailMapping);
OXMailMapping.addListNotInCache = OXAbstractMapping.addListNotInCache(OXMailMapping);
OXMailMapping.addListExpired = OXAbstractMapping.addListExpired(OXMailMapping);
OXMailMapping.addListMissingColumn = OXAbstractMapping.addListMissingColumn(OXMailMapping);
OXMailMapping.request = OXAbstractMapping.request(OXMailMapping);
OXMailMapping.handleResponse = OXAbstractMapping.handleResponse(OXMailMapping);
OXMailMapping.handleCriteriaNotInCache = OXAbstractMapping.handleCriteriaNotInCache(OXMailMapping);
OXMailMapping.handleCriteriaExpired = OXAbstractMapping.handleCriteriaExpired(OXMailMapping);
OXMailMapping.handleCriteriaMissingColumn = OXAbstractMapping.handleCriteriaMissingColumn(OXMailMapping);
OXMailMapping.handleCriteria=OXAbstractMapping.handleCriteria(OXMailMapping);
OXMailMapping.handleListNotInCache = OXAbstractMapping.handleListNotInCache(OXMailMapping);
OXMailMapping.handleListExpired = OXAbstractMapping.handleListExpired(OXMailMapping);
OXMailMapping.handleListMissingColumn = OXAbstractMapping.handleListMissingColumn(OXMailMapping);
OXMailMapping.handleList = OXAbstractMapping.handleList(OXMailMapping);
OXMailMapping.getCollection= OXAbstractMapping.getCollection(OXMailMapping);
OXMailMapping.convertColumns = OXAbstractMapping.convertColumns(OXMailMapping);
OXMailMapping.createCacheObject = OXAbstractMapping.createCacheObject(OXMailMapping);
OXMailMapping.checkAddCriteria = OXAbstractMapping.checkAddCriteria(OXMailMapping);
OXMailMapping.checkAddEditDeleteObject= OXAbstractMapping.checkAddEditDeleteObject(OXMailMapping);
OXMailMapping.getSubCollectionsfromObject = OXAbstractMapping.getSubCollectionsfromObject(OXMailMapping);
OXMailMapping.getSubCollections = OXAbstractMapping.getSubCollections(OXMailMapping);
OXMailMapping.equalsCollection = OXAbstractMapping.equalsCollection(OXMailMapping);
OXMailMapping.errorHandler = function(uniqueName,request,response,response2) {
    if(debug) { 
    	console.debug("Error",uniqueName,request,response,response2); 
    }
    if (response.error) {
    	// overwrite the default error handler at some special cases
        switch (response.code) {
        	 case "MSG-0032":
               triggerEvent("OX_Global_Error", 4, formatError(response));
        	   // message has been already deleted so we remove it from the cache
        	   // and ignore the error  
    	       OXMailMapping.deleteObjectsInternal([ request.object ]);
        	   break;
        	 default:
        	   // calling the default error handler
        	   OXCache.errorHandler(uniqueName,request,response,response2);
        	   break;        	
        }
    }
    return;
};

OXMailMapping.deleteObjectsInternal = function(objects) {
	var objects_tmp = new Array(objects.length);
	for (var i=0; i<objects.length; i++) {
		objects_tmp[i] = objects[i];
	}
	
    var tmpFn=OXCache.join.add();
    var oxMapping=OXMailMapping;

    // Calling all "before" callbacks.
    for(var i in OXCache.modifiedCallbacks) {
        var mc = OXCache.modifiedCallbacks[i];
        if (mc.module == oxMapping.module && mc.before) mc.before();
    }
    
    var tmpCollect=[];
    for(var mail=0;mail<objects_tmp.length;mail++) {
        var collections=oxMapping.getSubCollectionsfromObject(objects_tmp[mail],true);
        for(var count=0;count<collections.length;count++) {
            if(collections[count].check) { continue; } 
            var index=collections[count].map_objects.get(objects_tmp[mail]);
            if(index) {
                 collections[count].modified=true;
                 if(!collections[count].tmpIndexes) { collections[count].tmpIndexes=[]; }
                 collections[count].tmpIndexes.push(index);
                 tmpCollect.push(collections[count]);
                 collections[count].modifiedtmp=true;
             }               
        }
    }
    
    for(var count=0;count<tmpCollect.length;count++) {
        if(tmpCollect[count].modifiedtmp) {
                var indexes=tmpCollect[count].tmpIndexes;
                indexes.sort(function(a,b) { return a-b; });
                for(var i=indexes.length-1;i>=0;i--) {
                    tmpCollect[count].objects.splice(indexes[i]-1,1);
                }
                delete tmpCollect[count].tmpIndexes;
            tmpCollect[count].map_objects=new LRUKeyList();
            for(var index=0;index<tmpCollect[count].objects.length;index++) { 
                tmpCollect[count].map_objects.set(tmpCollect[count].objects[index],index+1) 
            }
            tmpCollect[count].modifiedtmp=false;
        }
    }

    for(var i2 in OXCache.modifiedCallbacks) {
        var mc = OXCache.modifiedCallbacks[i2];
        if (mc.module == oxMapping.module && !(mc.collection.criteria)) {
            for (var count = 0; count < mc.collection.objects.length; count++) {
                for (var i=0;i<objects_tmp.length;i++) {
                    if (mc.collection.objects[count].equals(objects_tmp[i])) {
                        mc.collection.objects.splice(count,1);
                        count--;
                        mc.modified=true;
                        break;
                   }
                }
            }
        }
    }
    tmpFn();
}
OXMailMapping.deleteObjects = function(mailObjects,harddelete,optCb) {
    var oxMapping=OXMailMapping;
    var request=new Array();
    var idArray=new Array();
    for(var i=0;i<mailObjects.length;i++) {
        idArray.push({id : mailObjects[i].id , folder : mailObjects[i].folder_id});
    }
    if(idArray.length) {
        var req = {
            module : "mail",
            action : "delete",
            folder : idArray[0].folder,
            data : idArray
        };
        if (harddelete) req.harddelete = 1;
        request.push(req);
    }
    var tmpFn=OXCache.join.add();
    var callit=false;
	json.put(AjaxRoot + "/multiple?session=" + session + "&continue=true",
        request, null, function(reply) {
            var tmpCollect=new Array();
            for(var i=0;i<reply.length;i++) {
                if(reply[i].error) {
                    if (reply[i].code == "MSG-0039") {
                        function cbyes() {
                            oxMapping.deleteObjects(mailObjects, true, optCb);
                        }
                        newConfirm(_("Quota Exceed"),_("Permanently remove your deleted E-Mails?"),AlertPopup.YESNO,null,null,cbyes,null);          
                    } else {
                       newServerError(reply[i]);
                    }
                } else {
                    oxMapping.deleteObjectsInternal(mailObjects);
                    oxMapping.createObjectsInternal([{ 
                        folder_id: configGetKey("mail.folder.trash"), 
                        module: "mail" 
                    }]);
					callit=true;		
                }
            }
            tmpFn();            
			if(optCb && callit) optCb();
        }
    );
}
OXMailMapping.setMailFlags = function(flag,objects) {
    var oxMapping=OXMailMapping;
    var mailFlagsParam = OXMailMapping.flagmapping[flag];
    var Self = this;
    var tmpObject = {};
    if(objects.length != 0) {
        var tmpFn=OXCache.join.add();
        var multipleObject = [];
        var folders = [], folder;
        // loop
        for (var i = 0; i < objects.length; i++) {
            // get folder
            folder = objects[i].folder || objects[i].folder_id;
            folders.push(folder);
            // add request
            multipleObject.push({ 
                action : "update", module : "mail", timestamp : 0, id : objects[i].id, 
                folder : folder, data : { flags : mailFlagsParam.flag , value :  mailFlagsParam.bool }
            }); 
         }
         (new JSON).put(AjaxRoot + "/multiple?session=" + session + "&continue=true",multipleObject,null,
         function(daten){
            if (flag == "spam" || flag == "ham") {
                oxMapping.deleteObjectsInternal(objects);
            } else {
                oxMapping.editObjectsInternal(objects,null,true,[mailFlagsParam]);
            }
            tmpFn();
            // update folder
            ox.api.folder.getMultiple({
                list: folders, // no success handler
                ignoreCache: true
            });
         });
     }
}
OXMailMapping.editObjectsInternal = function(oldObjects,changes,checkExist,optionalFlags) {
    var oxMapping=OXMailMapping;
    var tmpFn=OXCache.join.add();
    for(var myObject in oldObjects) {
        var tmpKeyOld=OXMailMapping.createKeyFromObject(oldObjects[myObject]);
        var collections=oxMapping.getSubCollectionsfromObject(oldObjects[myObject],true);
        //TODO Check Changes in Collection
        for(var i=0;i<collections.length;i++) {
            if(!collections[i].check) {
                //TODO SET CHECK IF CHANGES IN ORDER COLUMN
                //collections[i].check=true;
				collections[i].modified = true;
            } 
        }
        for(var i in OXCache.modifiedCallbacks) {
            if(OXCache.modifiedCallbacks[i].module == oxMapping.module && !(OXCache.modifiedCallbacks[i].collection.criteria)) {
                for(var count=0;count<OXCache.modifiedCallbacks[i].collection.objects.length;count++) {
                    if(tmpKeyOld.equals(OXCache.modifiedCallbacks[i].collection.objects[count])) {
                        var mc = OXCache.modifiedCallbacks[i];
                        if (!mc.modified && mc.before) mc.before();
                        mc.modified=true;    
                    }
                }
            }
        }
        var tmpObjectOld=OXCache.cachedObjects.get(tmpKeyOld);
        if(tmpObjectOld) {
           if(changes) {
              for(var i in changes) {
                  if(!checkExist || tmpObjectOld[i]!== undefined) {
                      tmpObjectOld[i]=changes[i];
                  }
              }
           }
           if(optionalFlags) {
              if(!checkExist || tmpObjectOld["flags"]!== undefined) {
                 for(var i in optionalFlags) {
                      if (optionalFlags[i].flag & 32) {
                          // was localUpdate
                          ox.api.folder.update({
                              local: true,
                              folder: tmpObjectOld.folder_id || tmpObjectOld.folder,
                              filter: function (data) {
                                  data.unread += ((tmpObjectOld.flags & 32) >> 5)
                                               - optionalFlags[i].bool;
                                  return data;
                              }
                          });
                      }
                      if((tmpObjectOld["flags"] & optionalFlags[i].flag) && !optionalFlags[i].bool) {
                        tmpObjectOld["flags"]=tmpObjectOld["flags"]-optionalFlags[i].flag;
                      } else if(!(tmpObjectOld["flags"] & optionalFlags[i].flag) && optionalFlags[i].bool) {
                        tmpObjectOld["flags"]=tmpObjectOld["flags"]+optionalFlags[i].flag;
                      }                        
                 }
              }
           }
       }
    }  
    OXCache.cachedObjects.set(tmpKeyOld,tmpObjectOld);
    //TODO CHECK CHANGE FOLDER
    tmpFn();
}
OXMailMapping.createObjectsInternal = function(newObjects) {
    setTimeout(function() {
        var oxMapping=OXMailMapping;

        // Calling all "before" callbacks.
        for(var i in OXCache.modifiedCallbacks) {
            var mc = OXCache.modifiedCallbacks[i];
            if (mc.module == oxMapping.module && mc.before) mc.before();
        }
        
        var tmpFn=OXCache.join.add();
        for(var object in newObjects) {
            var collections=oxMapping.getSubCollectionsfromObject(newObjects[object],true);
            for(var i=0;i<collections.length;i++) {
                if(!collections[i].check) {
                    collections[i].check=true;
                }
            }
        }
        tmpFn();
    },0);
}
OXMailMapping.editObject = function(oldObject,newObject) {
    setTimeout(function() {
        var oxMapping=OXMailMapping;
        var tmpFn=OXCache.join.add();
        var tmpKeyOld=OXMailMapping.createKeyFromObject(oldObject);
        var collections=oxMapping.getSubCollectionsfromObject(oldObject,true);
        for(var i=0;i<collections.length;i++) {
            if(!collections[i].check) {
                collections[i].check=true;
            }
        }
        for(var i in OXCache.modifiedCallbacks) {
            if(OXCache.modifiedCallbacks[i].module == oxMapping.module && !(OXCache.modifiedCallbacks[i].collection.criteria)) {
                for(var count=0;count<OXCache.modifiedCallbacks[i].collection.objects.length;count++) {
                    if(tmpKeyOld.equals(OXCache.modifiedCallbacks[i].collection.objects[count])) {
                        var mc = OXCache.modifiedCallbacks[i];
                        if (!mc.modified && mc.before) mc.before();
                        mc.modified=true;  
                    }
                }
            }
        }
        var tmpObjectOld=OXCache.cachedObjects.get(tmpKeyOld);
        OXCache.cachedObjects.set(tmpKeyOld,tmpObjectOld);
        //TODO CHECK CHANGE FOLDER
        tmpFn();
    },0);
}
OXMailMapping.createObject = function(newObject) {
    setTimeout(function() {
        var oxMapping=OXTaskMapping;

        // Calling all "before" callbacks.
        for(var i in OXCache.modifiedCallbacks) {
            var mc = OXCache.modifiedCallbacks[i];
            if (mc.module == oxMapping.module && mc.before) mc.before();
        }
        
        var tmpFn=OXCache.join.add();
        var collections=oxMapping.getSubCollectionsfromObject(newObject,true);
        for(var i=0;i<collections.length;i++) {
            if(!collections[i].check) {
                collections[i].check=true;
            }
        }
        tmpFn();
    },0);
}
OXMailMapping.setTag = OXAbstractMapping.setTag(OXMailMapping);

OXCache.setMapping("mail",OXMailMapping);




function OXInfoStoreMapping() {}
OXInfoStoreMapping.mandatoryfields=["folder_id","id","last_modified","created_by", "creation_date"];
OXInfoStoreMapping.idmapping= {
    "1": "id", "2": "created_by", "3": "modified_by", "4": "creation_date", "5": "last_modified", 
    "20": "folder_id", "100": "categories", "700": "title", "701": "url", 
    "702": "filename", "703": "file_mimetype", "704": "file_size", "705": "version", "706": "description", 
    "707": "locked_until", "708": "file_md5sum", "709": "version_comment", "710": "current_version", 
    "711": "number_of_versions", "102": "color_label"
}
// "101": "private_flag",
OXInfoStoreMapping.stringmapping=switchStringObject(OXInfoStoreMapping.idmapping);
OXInfoStoreMapping.module="infostore";
OXInfoStoreMapping.createKeyFromData = function(myArray,timestamp) {
     var tmp=new OXInfoStoreObjectCache();
     tmp.module = OXInfoStoreMapping.module;
     tmp.folder_id=myArray[0];
     tmp.id=myArray[1];
     tmp.created_by = myArray[3];
     if(timestamp) { tmp.timestamp = timestamp }
     return tmp;     
}
OXInfoStoreMapping.createKeyFromObject = function(myObject) {
     var tmp=new OXInfoStoreObjectCache();
     tmp.module = OXInfoStoreMapping.module;
     tmp.folder_id=myObject["folder_id"];
     tmp.id=myObject["id"];
     tmp.created_by = myObject.created_by;
     return tmp;     
}

OXInfoStoreMapping.objectConstructor=OXInfoStoreObjectCache;
OXInfoStoreMapping.stringmapping=switchStringObject(OXInfoStoreMapping.idmapping);
OXInfoStoreMapping.get = OXAbstractMapping.get(OXInfoStoreMapping);
OXInfoStoreMapping.execute = OXAbstractMapping.execute(OXInfoStoreMapping);
OXInfoStoreMapping.buildResponse = OXAbstractMapping.buildResponse(OXInfoStoreMapping);
OXInfoStoreMapping.criteriaRequest= OXAbstractMapping.criteriaRequest(OXInfoStoreMapping);
OXInfoStoreMapping.listRequest= OXAbstractMapping.listRequest(OXInfoStoreMapping);
OXInfoStoreMapping.addCriteriaNotInCache = OXAbstractMapping.addCriteriaNotInCache(OXInfoStoreMapping);
OXInfoStoreMapping.addCriteriaExpired = OXAbstractMapping.addCriteriaExpired(OXInfoStoreMapping);
OXInfoStoreMapping.addCriteriaMissingColumn = OXAbstractMapping.addCriteriaMissingColumn(OXInfoStoreMapping);
OXInfoStoreMapping.addListNotInCache = OXAbstractMapping.addListNotInCache(OXInfoStoreMapping);
OXInfoStoreMapping.addListExpired = OXAbstractMapping.addListExpired(OXInfoStoreMapping);
OXInfoStoreMapping.addListMissingColumn = OXAbstractMapping.addListMissingColumn(OXInfoStoreMapping);
OXInfoStoreMapping.request = OXAbstractMapping.request(OXInfoStoreMapping);
OXInfoStoreMapping.handleResponse = OXAbstractMapping.handleResponse(OXInfoStoreMapping);
OXInfoStoreMapping.handleCriteriaNotInCache = OXAbstractMapping.handleCriteriaNotInCache(OXInfoStoreMapping);
OXInfoStoreMapping.handleCriteriaExpired = OXAbstractMapping.handleCriteriaExpired(OXInfoStoreMapping);
OXInfoStoreMapping.handleCriteriaMissingColumn = OXAbstractMapping.handleCriteriaMissingColumn(OXInfoStoreMapping);
OXInfoStoreMapping.handleCriteria=OXAbstractMapping.handleCriteria(OXInfoStoreMapping);
OXInfoStoreMapping.handleListNotInCache = OXAbstractMapping.handleListNotInCache(OXInfoStoreMapping);
OXInfoStoreMapping.handleListExpired = OXAbstractMapping.handleListExpired(OXInfoStoreMapping);
OXInfoStoreMapping.handleListMissingColumn = OXAbstractMapping.handleListMissingColumn(OXInfoStoreMapping);
OXInfoStoreMapping.handleList = OXAbstractMapping.handleList(OXInfoStoreMapping);
OXInfoStoreMapping.getCollection= OXAbstractMapping.getCollection(OXInfoStoreMapping);
OXInfoStoreMapping.convertColumns = OXAbstractMapping.convertColumns(OXInfoStoreMapping);
OXInfoStoreMapping.createCacheObject = OXAbstractMapping.createCacheObject(OXInfoStoreMapping);
OXInfoStoreMapping.checkAddCriteria = OXAbstractMapping.checkAddCriteria(OXInfoStoreMapping);
OXInfoStoreMapping.checkAddEditDeleteObject= OXAbstractMapping.checkAddEditDeleteObject(OXInfoStoreMapping);
OXInfoStoreMapping.getSubCollectionsfromObject = OXAbstractMapping.getSubCollectionsfromObject(OXInfoStoreMapping);
OXInfoStoreMapping.getSubCollections = OXAbstractMapping.getSubCollections(OXInfoStoreMapping);
OXInfoStoreMapping.equalsCollection = OXAbstractMapping.equalsCollection(OXInfoStoreMapping);
OXInfoStoreMapping.errorHandler = OXCache.errorHandler;
OXInfoStoreMapping.editObjects = function(oldObjects,changes) {
    setTimeout(function() {
       var tmpFn=OXCache.join.add();
       OXInfoStoreMapping.editObjectsInternal(oldObjects,changes);
       tmpFn(); 
    },0);
}
OXInfoStoreMapping.editObjectsInternal = function(oldObjects,changes,checkExist) {
    var oxMapping=OXInfoStoreMapping;
    var tmpFn=OXCache.join.add();
    for(var myObject in oldObjects) {
        var tmpKeyOld=oxMapping.createKeyFromObject(oldObjects[myObject]);
        var collections=oxMapping.getSubCollectionsfromObject(oldObjects[myObject],true);
        //TODO Check Changes in Collection
        for(var i=0;i<collections.length;i++) {
            if(!collections[i].search && !collections[i].check) {
                collections[i].check=true;
            } 
        }
        for(var i in OXCache.modifiedCallbacks) {
            if(OXCache.modifiedCallbacks[i].module == oxMapping.module && !(OXCache.modifiedCallbacks[i].collection.criteria)) {
                for(var count=0;count<OXCache.modifiedCallbacks[i].collection.objects.length;count++) {
                    if(tmpKeyOld.equals(OXCache.modifiedCallbacks[i].collection.objects[count])) {
                        var mc = OXCache.modifiedCallbacks[i];
                        if (!mc.modified && mc.before) mc.before();
                        mc.modified=true; 
                    }
                }
            }
        }
        var tmpObjectOld=OXCache.cachedObjects.get(tmpKeyOld);
        if(tmpObjectOld) {
           if(changes) {
              for(var i in changes) {
                  if(!checkExist || tmpObjectOld[i]!== undefined) {
                      if(i != "notification") {
                          tmpObjectOld[i]=changes[i];
                      }
                  }
              }
           }
       }
       OXCache.cachedObjects.set(tmpKeyOld,tmpObjectOld);    
    }  
    //TODO CHECK CHANGE FOLDER
    tmpFn();
}
OXInfoStoreMapping.deleteObjects = function(objects, callback) {
    var oxMapping=OXInfoStoreMapping;
    var request = new Array();
    for (var i in objects) {
        var tmpTime=objects[i]["timestamp"]; 
        if (!tmpTime) {
            var tmpObject = OXCache.cachedObjects.get(objects[i]);
            if(tmpObject) {
                tmpTime=tmpObject.timestamp;
            }
        }
		request.push({
            module: oxMapping.module, 
            action: "delete",
            timestamp: tmpTime,
            data: [{ folder : objects[i]["folder_id"] , id : objects[i]["id"] }]
        });
    }
    json.put(AjaxRoot + "/multiple?session=" + session + "&continue=true",
        request, null, function(reply) {
            var tmpFn=OXCache.join.add();
            var error=new Array();
            for(var i=0;i<reply.length;i++) {
                if(reply[i].error) {
                    newServerError(reply[i]);
                    error.push(i);   
                }  
            }
            if(!error.length) {
                oxMapping.deleteObjectsInternal(objects);
            }
            tmpFn();
            if (callback) callback();
        }
    );
}
OXInfoStoreMapping.deleteObjectsInternal = function(objects) {
    var tmpFn=OXCache.join.add();
    var oxMapping=OXInfoStoreMapping;

    // Calling all "before" callbacks.
    for(var i in OXCache.modifiedCallbacks) {
        var mc = OXCache.modifiedCallbacks[i];
        if (mc.module == oxMapping.module && mc.before) mc.before();
    }
    
    var tmpCollect=[];
    for(var mail=0;mail<objects.length;mail++) {
        var collections=oxMapping.getSubCollectionsfromObject(objects[mail],true);
        for(var count=0;count<collections.length;count++) {
            if(collections[count].check) { continue; } 
            var index=collections[count].map_objects.get(objects[mail]);
            if(index) {
                 collections[count].modified=true;
                 if(!collections[count].tmpIndexes) { collections[count].tmpIndexes=[]; }
                 collections[count].tmpIndexes.push(index);
                 tmpCollect.push(collections[count]);
                 collections[count].modifiedtmp=true;
             }               
        }
    }
    for(var count=0;count<tmpCollect.length;count++) {
        if(tmpCollect[count].modifiedtmp) {
                var indexes=tmpCollect[count].tmpIndexes;
                indexes.sort(function(a,b) { return a-b; });
                for(var i=indexes.length-1;i>=0;i--) {
                    tmpCollect[count].objects.splice(indexes[i]-1,1);
                }
                delete tmpCollect[count].tmpIndexes;
            tmpCollect[count].map_objects=new LRUKeyList();
            for(var index=0;index<tmpCollect[count].objects.length;index++) { 
                tmpCollect[count].map_objects.set(tmpCollect[count].objects[index],index+1) 
            }
            tmpCollect[count].modifiedtmp=false;
        }
    }
    tmpFn();
}
OXInfoStoreMapping.createObject = function(newObject) {
    setTimeout(function() {
        var tmpFn=OXCache.join.add();
        OXInfoStoreMapping.createObjectsInternal([newObject]);
        tmpFn();
    },0);
}
OXInfoStoreMapping.createObjectsInternal = function(newObjects) {
    setTimeout(function() {
        var oxMapping=OXInfoStoreMapping;

        // Calling all "before" callbacks.
        for(var i in OXCache.modifiedCallbacks) {
            var mc = OXCache.modifiedCallbacks[i];
            if (mc.module == oxMapping.module && mc.before) mc.before();
        }
        
        var tmpFn=OXCache.join.add();
        for(var object in newObjects) {
            var collections=oxMapping.getSubCollectionsfromObject(newObjects[object],true);
            for(var i=0;i<collections.length;i++) {
                if(!collections[i].search && !collections[i].check) {
                    collections[i].check=true;
                }
            }
        }
        tmpFn();
    },0);
}
OXInfoStoreMapping.setTag = OXAbstractMapping.setTag(OXInfoStoreMapping);
OXCache.setMapping("infostore",OXInfoStoreMapping);
function OXAbstractObject() {   
    var Self=this;
    this.type="OXObject";
}
OXAbstractObject.prototype.hasColumns = function(stringobjects) {
	for(var i in stringobjects) {
	    var path = stringobjects[i].split("/");
	    if (path.length == 1) {
	        if (this[stringobjects[i]] === undefined) return false;
	    } else {
	        if (!this[path[0]]) return false;
	    }
    }
	return true;
}
OXAbstractObject.prototype.getColumns = function() {
    var fields=new Array();
    if(this.mapping) {
	   for(var i in this.mapping.stringmapping) {
	       if(this.i) {
                filledfields.push(i);
            } 
        }
    }
	return filledfields;
}
function OXTaskObjectCache() {
    this.module="tasks";
	this.mapping=OXTaskMapping;
}
OXTaskObjectCache.createfromObject = Key.createfromObject;
OXTaskObjectCache.prototype.equals=Key.prototype.equals;
OXTaskObjectCache.prototype.hashCode=Key.prototype.hashCode;
OXTaskObjectCache.prototype.hasColumns=OXAbstractObject.prototype.hasColumns;
OXTaskObjectCache.prototype.getColumns=OXAbstractObject.prototype.getColumns;
function OXCalendarObjectCache() {
    this.module="calendar";
	this.mapping=OXCalendarMapping;
}
OXCalendarObjectCache.createfromObject = Key.createfromObject;
OXCalendarObjectCache.prototype.equals=Key.prototype.equals;
OXCalendarObjectCache.prototype.hashCode=Key.prototype.hashCode;
OXCalendarObjectCache.prototype.hasColumns=OXAbstractObject.prototype.hasColumns;
OXCalendarObjectCache.prototype.getColumns=OXAbstractObject.prototype.getColumns;
function OXContactObjectCache() {
    this.module="contacts";
	this.mapping=OXContactMapping;
}
OXContactObjectCache.createfromObject = Key.createfromObject;
OXContactObjectCache.prototype.equals=Key.prototype.equals;
OXContactObjectCache.prototype.hashCode=Key.prototype.hashCode;
OXContactObjectCache.prototype.hasColumns=OXAbstractObject.prototype.hasColumns;
OXContactObjectCache.prototype.getColumns=OXAbstractObject.prototype.getColumns;
function OXMailObjectCache() {
    this.module="mail";
	this.mapping=OXMailMapping;
}
OXMailObjectCache.createfromObject = Key.createfromObject;
OXMailObjectCache.prototype.equals=Key.prototype.equals;
OXMailObjectCache.prototype.hashCode=Key.prototype.hashCode;

OXMailObjectCache.prototype.hasColumns=OXAbstractObject.prototype.hasColumns;
OXMailObjectCache.prototype.getColumns=OXAbstractObject.prototype.getColumns;
function OXInfoStoreObjectCache() {
    this.module="infostore";
	this.mapping=OXInfoStoreMapping;
}
OXInfoStoreObjectCache.createfromObject = Key.createfromObject;
OXInfoStoreObjectCache.prototype.equals=Key.prototype.equals;
OXInfoStoreObjectCache.prototype.hashCode=Key.prototype.hashCode;
OXInfoStoreObjectCache.prototype.hasColumns=OXAbstractObject.prototype.hasColumns;
OXInfoStoreObjectCache.prototype.getColumns=OXAbstractObject.prototype.getColumns;


  /////////////////////////////////////
 //   Tests and others              //
/////////////////////////////////////
var first=false;
function etest1(arg) {
    for(var i=0;i<arg;i++) {
    (new JSON()).put(AjaxRoot + "/tasks?action=new&session=" + session, {
        "title" : "Aufgabe Nr:"+i,
        "folder_id" : 3009
    }, null, function(cb){
    });
    }
}
var tmptime=new Date().getTime();
var times=new Array();   
var times2=new Array();  

register("OX_Refresh", function() {
	OXCache.update();
}) 
register("OX_Folder_Cleared",function(folder_id) {
	var tmpFn=OXCache.join.add();
	var allCollections=OXMailMapping.getSubCollectionsfromObject({ folder_id : folder_id });
	for (var i = 0; i < allCollections.length; i++) {
		OXMailMapping.deleteObjectsInternal(allCollections[i].objects);
	}	
	tmpFn();
});     
