diff --git a/process/ImporterCustomMappingFunctions_lib/ImporterCustomMappingFunctions_lib.aod b/process/ImporterCustomMappingFunctions_lib/ImporterCustomMappingFunctions_lib.aod
new file mode 100644
index 0000000000000000000000000000000000000000..6544d46839aa52bd3517add653f0eb0a8fb7e881
--- /dev/null
+++ b/process/ImporterCustomMappingFunctions_lib/ImporterCustomMappingFunctions_lib.aod
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
+  <name>ImporterCustomMappingFunctions_lib</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/ImporterCustomMappingFunctions_lib/process.js</process>
+  <variants>
+    <element>LIBRARY</element>
+  </variants>
+</process>
diff --git a/process/ImporterCustomMappingFunctions_lib/process.js b/process/ImporterCustomMappingFunctions_lib/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..bbfd65d5d7dccccbb1ea6066c73a7df03aea68d8
--- /dev/null
+++ b/process/ImporterCustomMappingFunctions_lib/process.js
@@ -0,0 +1,4 @@
+///////////////////////////////////////////////////////////////////
+/// custom toolkit methods for the import handler               ///
+/// edit this, since this is serperate vor every project        ///
+///////////////////////////////////////////////////////////////////
diff --git a/process/ImporterMappingFunctions_lib/ImporterMappingFunctions_lib.aod b/process/ImporterMappingFunctions_lib/ImporterMappingFunctions_lib.aod
new file mode 100644
index 0000000000000000000000000000000000000000..0e8acf1918c716ed3d774608965f64b1dba0d136
--- /dev/null
+++ b/process/ImporterMappingFunctions_lib/ImporterMappingFunctions_lib.aod
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
+  <name>ImporterMappingFunctions_lib</name>
+  <comment></comment>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/ImporterMappingFunctions_lib/process.js</process>
+  <variants>
+    <element>LIBRARY</element>
+  </variants>
+</process>
diff --git a/process/ImporterMappingFunctions_lib/process.js b/process/ImporterMappingFunctions_lib/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..05c4aa930bda24e17fd357797301b67bd8fce6d3
--- /dev/null
+++ b/process/ImporterMappingFunctions_lib/process.js
@@ -0,0 +1,1028 @@
+import("system.fileIO");
+import("system.SQLTYPES");
+import("system.text");
+import("system.db");
+import("system.vars");
+import("system.eMath");
+import("system.util");
+import("system.datetime");
+import("system.logging");
+import("Attribute_lib");
+import("Sql_lib");
+import("Importer_lib");
+
+/////////////////////////////////////////////////////////////////////
+/// toolkit methods for the import handler                      ///
+/// DO NOT TOUCH - use lib_importerCustomMappingFunctions       ///
+///////////////////////////////////////////////////////////////////
+
+/*
+* Values of the mapping line:
+* Keyword req -- the column index with the new keyword value
+* Container req -- the keyword container for the keyword lookup
+*
+* @name iKeyword
+* @param {Object} pObject req the mapping line
+* @return {Boolean} true
+**/
+function iKeyword(pObject) { 
+    if (!this.doIfCheck(pObject)) return true;
+
+    var keyword = this.InputRecord[pObject.Keyword];
+    if(keyword == undefined) keyword = this.resolveSymbol(pObject, pObject.Keyword);
+    var container = this.InputRecord[pObject.Container];
+    if(container == undefined) container = this.resolveSymbol(pObject, pObject.Container);
+
+    if(!keyword || !container)  return true;
+    
+    var sql = "select " + this.getColumnCase("keyid") + " from " + this.getTableCase("ab_keyword_entry") + " where " 
+    + this.getColumnCase("container") + " = ? and " + this.getColumnCase("title") + " = ?";
+    var id = db.cell([sql, [[container, SQLTYPES.VARCHAR], [keyword, SQLTYPES.VARCHAR]]], this.Config.AliasTo);
+    
+    if(id == "" || id == null) {
+        id = util.getNewUUID();
+        var columns = [this.getColumnCase("ab_keyword_entryid"), this.getColumnCase("keyid"), this.getColumnCase("container"),
+            this.getColumnCase("title"), this.getColumnCase("sorting"), this.getColumnCase("isactive"), this.getColumnCase("isessential")]; 
+        sql = "select max(coalesce(sorting, 0))+1 from ab_keyword_entry where container = ?";
+        var sort = db.cell([sql, [[container, SQLTYPES.VARCHAR]]], this.Config.AliasTo);
+        if(sort == "") sort = "0";
+        values = [id, util.getNewUUID(), container, keyword, sort, "1", "0"];
+        this.insertData(this.getTableCase("ab_keyword_entry"), columns, null, values, this.Config.AliasTo);
+    }
+    this.setOutput(pObject, id);
+    return true;
+}
+
+/*
+ * Values of the mapping line:
+ * Attribute req -- the new attribute name
+ * AType req -- the type of the attribute
+ * OType opt -- the type of the object (AB_ATTRIBUTEUSAGE)
+ * OID opt -- the row id for the object instance (AB_ATTRIBUTERELATION)
+ * Value opt -- the value for the object instance (AB_ATTRIBUTERELATION)
+ *
+ * @name iAttribute
+ * @param {Object} pObject req the mapping line
+ * @return {Boolean} true
+ * */
+function iAttribute(pObject) {
+    if (!this.doIfCheck(pObject)) return true;
+
+    var attribute = this.InputRecord[pObject.Attribute];
+    if(attribute == undefined) attribute = this.resolveSymbol(pObject, pObject.Attribute);
+    var atype = this.InputRecord[pObject.AType];
+    if(atype == undefined) atype = this.resolveSymbol(pObject, pObject.AType);
+    var otype = this.InputRecord[pObject.OType];
+    if(otype == undefined) otype = this.resolveSymbol(pObject, pObject.OType);
+    var oid = this.InputRecord[pObject.OID];
+    if(oid == undefined) oid = this.resolveSymbol(pObject, pObject.OID);
+    var value = this.InputRecord[pObject.Value];
+    if(value == undefined) value = this.resolveSymbol(pObject, pObject.Value);
+    
+    if (!attribute || !atype) return true;
+    atype = atype.toUpperCase();
+
+    var valueColumn = "";
+    var attributes = attribute.split(".");   
+    var columns = [this.getColumnCase("ab_attributeid"), this.getColumnCase("attribute_parent_id"), this.getColumnCase("attribute_name"), 
+        this.getColumnCase("attribute_type"), this.getColumnCase("attribute_level"), this.getColumnCase("attribute_active")];
+    var type = $AttributeTypes.GROUP.toString();
+    switch (atype) {
+        case $AttributeTypes.TEXT.toString():
+            valueColumn = this.getColumnCase("char_value");
+            break;
+        case $AttributeTypes.DATE.toString():
+            valueColumn = this.getColumnCase("date_value");
+            break;
+        case $AttributeTypes.NUMBER.toString():
+            valueColumn = this.getColumnCase("number_value");
+            break;
+        case $AttributeTypes.BOOLEAN.toString():
+            valueColumn = this.getColumnCase("bool_value");
+            break;
+        case $AttributeTypes.COMBO.toString():
+            valueColumn = this.getColumnCase("id_value");
+            type = $AttributeTypes.COMBO.toString();
+            break;
+        default:
+            return true;
+    }
+
+    if (this.FuncBuffer.iAttribute == undefined) this.FuncBuffer.iAttribute = {childs: {}};
+    var pathToFollow = this.FuncBuffer.iAttribute;
+    for (var i = 0; i < attributes.length; i++)  {
+        if (pathToFollow["childs"][attributes[i]] != undefined) {
+            var id = pathToFollow["childs"][attributes[i]]["id"];            
+        } else {
+            pathToFollow["childs"][attributes[i]] = {id: id, childs: {}};
+            if (i == 0) {
+                var parent = "NULL";  
+                var sql = "select " + this.getColumnCase("ab_attributeid") + " from  " + this.getTableCase("ab_attribute") + " where " 
+                + this.getColumnCase("attribute_name") + " = ? and " + this.getColumnCase("attribute_level") + " = 0";
+                id = db.cell([sql, [[attributes[i], SQLTYPES.VARCHAR]]], this.Config.AliasTo);                         
+            } else {
+                parent = pathToFollow["id"];
+                sql = "select " + this.getColumnCase("ab_attributeid") + " from " + this.getTableCase("ab_attribute") + " where " 
+                    + this.getColumnCase("attribute_name") + " = ? and " + this.getColumnCase("attribute_parent_id") + " = ?";
+                id = db.cell([sql, [[attributes[i], SQLTYPES.VARCHAR], [parent, SQLTYPES.CHAR]]], this.Config.AliasTo);                         
+            }       
+            if (id == "" || id == null) {
+                id = util.getNewUUID();
+                if (attributes.length == i+1) type = atype;
+                //TODO: add insertNoWait to instantly add AB_ATTRIBUTE records; this ensures that nothing is in the funcBuffer that does not exist in the database
+                //TODO: check: are COMOB-values added automatically?
+                this.insertData(this.getTableCase("ab_attribute"), columns, null, [id, parent, attributes[i], type, i.toString(), "1"], this.Config.AliasTo);
+            }
+            pathToFollow["childs"][attributes[i]]["id"] = id;
+        }
+        pathToFollow = pathToFollow["childs"][attributes[i]];
+    } 
+    
+    if (otype) {
+        var aid = id;
+        sql = "select " + this.getColumnCase("ab_attributeusageid") + " from " + this.getTableCase("ab_attributeusage") + " where "
+            + this.getColumnCase("ab_attribute_id") + " = ? and " + this.getColumnCase("object_type") + " = ?";
+        id = db.cell([sql, [[aid, SQLTYPES.CHAR], [otype, SQLTYPES.VARCHAR]]], this.Config.AliasTo);      
+        if (id == "" || id == null) {
+            columns = [this.getColumnCase("ab_attributeusageid"), this.getColumnCase("ab_attribute_id"), this.getColumnCase("object_type")];
+            this.insertData(this.getTableCase("ab_attributeusage"), columns, null, [util.getNewUUID(), aid, otype], this.Config.AliasTo);
+        } 
+        
+        if (value && oid) {
+            sql = "select " + this.getColumnCase("ab_attributerelationid") + " from " + this.getTableCase("ab_attributerelation") + " where " 
+                 + this.getColumnCase("ab_attribute_id") + " = ? and " + this.getColumnCase("object_rowid") + " = ? and "
+                 + this.getColumnCase("object_type") + " = ?";
+            id = db.cell([sql, [[aid, SQLTYPES.CHAR], [oid, SQLTYPES.CHAR], [otype, SQLTYPES.VARCHAR]]], this.Config.AliasTo);
+            if (id == "" || id == null) {
+                columns = [this.getColumnCase("ab_attributerelationid"), this.getColumnCase("ab_attribute_id"), this.getColumnCase("object_type"),
+                     this.getColumnCase("object_rowid"), valueColumn];
+                 this.insertData(this.getTableCase("ab_attributerelation"), columns, null, [util.getNewUUID(), aid, otype, oid, value], this.Config.AliasTo);
+            } else if (this.Config.ImportCommand.indexOf("update") != -1) {
+                cond = this.getColumnCase("ab_attributerelationid") + " = '" + id + "'";
+                this.updateData(this.getTableCase("ab_attributerelation"), [valueColumn], null, [value], cond, this.Config.AliasTo);
+            }
+        }
+    }    
+    return true;
+}
+
+/*
+ * Values of the mapping line:
+ * Attribute req -- the column index with the new attribute value
+ * AType req -- the type of the attribute
+ * Container req -- the container name of the keyword
+ * Keyword opt -- a new keyword name or an existing KeyId (AB_KEYWORD_ATTRIBUTERELATION)
+ * Value opt - the value of the relation (AB_KEYWORD_ATTRIBUTERELATION)
+ *
+ * @name iKeywordAttribute
+ * @param {Object} pObject req the mapping line
+ * @return {Boolean} true
+ * */
+function iKeywordAttribute(pObject) {
+    if (!this.doIfCheck(pObject)) return true;
+    
+    var attribute = this.InputRecord[pObject.Attribute];
+    if(attribute == undefined) attribute = this.resolveSymbol(pObject, pObject.Attribute);
+    var atype = this.InputRecord[pObject.AType];
+    if(atype == undefined) atype = this.resolveSymbol(pObject, pObject.AType);
+    var container = this.InputRecord[pObject.Container];
+    if(container == undefined) container = this.resolveSymbol(pObject, pObject.Container);
+    var keyword = this.InputRecord[pObject.Keyword];
+    if(keyword == undefined) keyword = this.resolveSymbol(pObject, pObject.Keyword);
+    var value = this.InputRecord[pObject.Value];
+    if(value == undefined) value = this.resolveSymbol(pObject, pObject.Value);
+    
+    if (!attribute || !container || !atype) return true;
+    atype = atype.toUpperCase();
+    
+    var valueColumn = "";
+    switch (atype) {
+        case $AttributeTypes.TEXT.toString():
+            valueColumn = this.getColumnCase("char_value");
+            break;
+        case $AttributeTypes.NUMBER.toString():
+            valueColumn = this.getColumnCase("number_value");           
+            break;
+        case $AttributeTypes.BOOLEAN.toString():
+            valueColumn = this.getColumnCase("bool_value");         
+            break;
+        default:
+            return true;
+    }
+    
+    var sql = "select " + this.getColumnCase("ab_keyword_attributeid") + " from  " + this.getTableCase("ab_keyword_attribute") 
+        + " where " + this.getColumnCase("name") + " = ? and " + this.getColumnCase("container") + " = ?";
+    var aid = db.cell([sql, [[attribute, SQLTYPES.VARCHAR], [container, SQLTYPES.VARCHAR]]], this.Config.AliasTo); 
+    if (aid == "" || aid == null) {
+        aid = util.getNewUUID();
+        var columns = [this.getColumnCase("ab_keyword_attributeid"), this.getColumnCase("name"), this.getColumnCase("container"), this.getColumnCase("type")];
+        this.insertData(this.getTableCase("ab_keyword_attribute"), columns, null, [aid, attribute, container, atype], this.Config.AliasTo);
+    }
+    
+    if (keyword && value) {   
+        sql = "select " + this.getColumnCase("keyid") + " from " + this.getTableCase("ab_keyword_entry") + " where " 
+            + this.getColumnCase("keyid") + " = ?";
+        var kid = db.cell([sql, [[keyword, SQLTYPES.CHAR]]], this.Config.AliasTo);      
+        
+        if (kid == "" || kid == null) {
+            sql = "select " + this.getColumnCase("keyid") + " from " + this.getTableCase("ab_keyword_entry") + " where " 
+                + this.getColumnCase("container") + " = ? and " + this.getColumnCase("title") + " = ?";
+            kid = db.cell([sql, [[container, SQLTYPES.VARCHAR], [keyword, SQLTYPES.VARCHAR]]], this.Config.AliasTo);
+            if (kid == "" || kid == null) {
+                columns = [this.getColumnCase("ab_keyword_entryid"), this.getColumnCase("keyid"), this.getColumnCase("container"),
+                    this.getColumnCase("title"), this.getColumnCase("sorting"), this.getColumnCase("isactive"), this.getColumnCase("isessential")]; 
+                sql = "select max(coalesce(sorting, 0))+1 from ab_keyword_entry where container = ?";
+                var sort = db.cell([sql, [[container, SQLTYPES.VARCHAR]]], this.Config.AliasTo);
+                if(sort == "") sort = "0";
+                kid = util.getNewUUID();
+                this.insertData(this.getTableCase("ab_keyword_entry"), columns, null, 
+                    [kid, util.getNewUUID(), container, keyword, sort, "1", "0"], this.Config.AliasTo);
+            }
+        }
+        
+        sql = "select " + this.getColumnCase("ab_keyword_attributerelationid") + " from " + this.getTableCase("ab_keyword_attributerelation") 
+            + " where " + this.getColumnCase("ab_keyword_entry_id") + " = ? and " + this.getColumnCase("ab_keyword_attribute_id") + " = ?";
+        id = db.cell([sql, [[kid, SQLTYPES.CHAR], [aid, SQLTYPES.CHAR]]], this.Config.AliasTo);
+        if (id == "" || id == null) {
+            columns = [this.getColumnCase("ab_keyword_attributerelationid"), this.getColumnCase("ab_keyword_entry_id"), 
+                this.getColumnCase("ab_keyword_attribute_id"), valueColumn];
+            id = util.getNewUUID();
+            this.insertData(this.getTableCase("ab_keyword_attributerelation"), columns, null, [id, kid, aid, value], this.Config.AliasTo);
+        } else {
+            if (this.Config.ImportCommand.indexOf("update") != -1) {
+                cond = this.getColumnCase("ab_keyword_attributerelationid") + " = '" + id + "'";
+                this.updateData(this.getTableCase("ab_keyword_attributerelation"), [valueColumn], null, [value], cond, this.Config.AliasTo);
+            }            
+        }       
+        this.setOutput(pObject, id);
+    } else {
+        this.setOutput(pObject, aid);
+    }
+    return true;  
+}
+
+/*
+ * Values of the mapping line:
+ * Address req -- the address for the communication entry
+ * Medium req -- the medium id
+ * ContactID req -- the id of the entry in the contact table
+ * Standard opt -- the standard value (boolean)
+ *
+ * @name iComm
+ * @param {Object} pObject req the mapping line
+ * @return {Boolean} true
+ * */
+function iComm(pObject) {
+    if (! this.doIfCheck(pObject)) return true;
+
+    var address = this.InputRecord[pObject.Address];
+    if(address == undefined) address = this.resolveSymbol(pObject, pObject.Address);
+    var medium = this.InputRecord[pObject.Medium];
+    if(medium == undefined) medium = this.resolveSymbol(pObject, pObject.Medium);
+    var contact = this.InputRecord[pObject.ContactID];
+    if(contact == undefined) contact = this.resolveSymbol(pObject, pObject.ContactID);
+    var standard = "0";
+    if(pObject.Standard) standard = "1";
+    
+    if(!address || !medium || !contact) return true;
+    
+    var sql = "select " + this.getColumnCase("communicationid") + " from " + this.getTableCase("communication")
+        +" where " + this.getColumnCase("contact_id") + " = ? and " + this.getColumnCase("medium_id") + " = ? and "
+        + this.getColumnCase("standard") + " = ? and " + this.getColumnCase("addr") + " = ?"
+    var id = db.cell([sql, [[contact, SQLTYPES.CHAR], [medium, SQLTYPES.INTEGER], 
+            [standard, SQLTYPES.SMALLINT], [address, SQLTYPES.VARCHAR]]], this.Config.AliasTo);
+    if (id == "" || id == null) {
+        var columns = [this.getColumnCase("communicationid"), this.getColumnCase("addr"), 
+            this.getColumnCase("medium_id"), this.getColumnCase("contact_id"), this.getColumnCase("standard")];
+        this.insertData(this.getTableCase("communication"), columns, null, [util.getNewUUID(), address, medium, contact, standard], this.Config.AliasTo);       
+    }    
+    return true;
+}
+
+/*
+ * Values of the mapping line:
+ * Reason opt -- the reason 
+ * Medium req -- the medium id
+ * ContactID req -- the id of the entry in the contact table
+ * Type req -- yes or no to communication
+ *
+ * @name iCommRestriction
+ * @param {Object} pObject req the mapping line
+ * @return {Boolean} true
+ * */
+function iCommRestriction(pObject) {
+    if (!this.doIfCheck(pObject)) return true;
+
+    var reason = this.InputRecord[pObject.Reason];
+    if(reason == undefined) reason = this.resolveSymbol(pObject, pObject.Reason);
+    var medium = this.InputRecord[pObject.Medium];
+    if(medium == undefined) medium = this.resolveSymbol(pObject, pObject.Medium);
+    var contact = this.InputRecord[pObject.ContactID];
+    if(contact == undefined) contact = this.resolveSymbol(pObject, pObject.ContactID);
+    var type = this.InputRecord[pObject.Type];
+    if(type == undefined) type = this.resolveSymbol(pObject, pObject.Type);
+    
+    if (!medium || !contact || !type) return true;
+
+    var sql = "select top 1 " + this.getColumnCase("type") + " from " + this.getTableCase("commrestriction")
+        +" where " + this.getColumnCase("contact_id") + " = ? and " + this.getColumnCase("medium") + " = ? order by " 
+        + this.getColumnCase("date_edit") + " desc, " + this.getColumnCase("date_new") + " desc";
+    var id = db.cell([sql, [[contact, SQLTYPES.CHAR], [medium, SQLTYPES.CHAR]]], this.Config.AliasTo);
+    if (id == "" || id == null || id != type) {
+        if(reason == undefined || reason == null) reason = "NULL";
+        var columns = [this.getColumnCase("commrestrictionid"), this.getColumnCase("medium"), this.getColumnCase("contact_id"), this.getColumnCase("type"), this.getColumnCase("reason")];
+        this.insertData(this.getTableCase("commrestriction"), columns, null, [util.getNewUUID(), medium, contact, type, reason], this.Config.AliasTo);       
+    } 
+    return true;
+}
+
+/*
+ * Values of the mapping line:
+ * ActivityID req -- the column specifier for the activity table
+ * OID req -- the id for a default object for object_rowid
+ * OType req -- the context name
+ *
+ * @name iActivityLink
+ * @param {Object} pObject req the mapping line
+ * @return {Boolean} true
+ * */
+function iActivityLink(pObject) {
+    if (!this.doIfCheck(pObject)) return true;
+
+    var aid = this.InputRecord[pObject.ActivityID];
+    if(aid == undefined) aid = this.resolveSymbol(pObject, pObject.ActivityID);
+    var otype = this.InputRecord[pObject.OType];
+    if(otype == undefined) otype = this.resolveSymbol(pObject, pObject.OType);
+    var oid = this.InputRecord[pObject.OID];
+    if(oid == undefined) oid = this.resolveSymbol(pObject, pObject.OID);
+    
+    if (!aid || !oid || !otype) return true; 
+    
+    var sql = "select " + this.getColumnCase("activitylinkid") + " from " + this.getTableCase("activitylink") + " where "
+        + this.getColumnCase("activity_id") + " = ? and " + this.getColumnCase("object_type") + " = ? and " + this.getColumnCase("object_rowid") + " = ?";
+    var id = db.cell([sql, [[aid, SQLTYPES.VARCHAR], [otype, SQLTYPES.VARCHAR], [oid, SQLTYPES.CHAR]]], this.Config.AliasTo);
+    if (id == "" || id == null) {
+        var columns = [this.getColumnCase("activitylinkid"), this.getColumnCase("activity_id"), this.getColumnCase("object_type"), this.getColumnCase("object_rowid")];
+        this.insertData(this.getTableCase("activitylink"), columns, null, [util.getNewUUID(), aid, otype, oid], this.Config.AliasTo);
+    }
+    return true;
+}
+
+/*
+* imports an document from a given path
+*
+* @param {Object} pObject req the mapping line
+*
+* @return {Boolean} true, if import of the data was successful, otherwise false
+*/
+function iDocumentByPath(pObject){
+    var resultDocument = true;
+    if (! this.doIfCheck(pObject)) return true;
+
+    try {
+        if(pObject.Rowid != "" && pObject.Filename != "") {
+            if(pObject.Value && pObject.Rowid) {          
+                var wert = this.resolveSymbol(pObject, pObject.Value);
+                var row = this.resolveSymbol(pObject, pObject.Rowid);
+                var dateNew = this.resolveSymbol(pObject, pObject.DateNew);           
+                var filename = this.InputRecord[pObject.Filename];
+                var data = fileIO.getData(wert, util.DATA_BINARY);
+                var length = fileIO.getLength(wert);
+                logging.log(filename + " " + dateNew)
+            }       
+            var sql = "select count(" + this.getColumnCase("row_id") + ") from " + this.getTableCase("asys_binaries") 
+                + " where " + this.getColumnCase("row_id") + " = ? and " + this.getColumnCase("filename") + " = ?";
+            var count = db.cell([sql, [[row, SQLTYPES.CHAR], [filename, SQLTYPES.VARCHAR]]], this.Config. AliasSys);
+            if(count == 0) {            
+                var cols = [this.getColumnCase("Id"), this.getColumnCase("Tablename"), this.getColumnCase("Datasize"),
+                    this.getColumnCase("date_new"), this.getColumnCase("date_edit"), this.getColumnCase("user_new"),
+                    this.getColumnCase("bindata"), this.getColumnCase("containername"), this.getColumnCase("filename"),
+                    this.getColumnCase("row_id"), this.getColumnCase("mimetype")];
+                var vals = [util.getNewUUID(), "$!GENERIC!$", length, dateNew, dateNew, vars.getString("$sys.user"), data, 
+                    "DOCUMENT", filename, row, util.getMimeType(filename)];
+                db.insertData(this.getTableCase("asys_binaries"), cols, null, vals, this.Config. AliasSys);
+            }
+        }
+    } catch(ex) {
+        logging.log("Datei nicht gefunden!");
+        resultDocument = false;
+    }
+    return resultDocument;
+}
+
+/*
+* imports an document
+* draft: Container: "string", Row: "TBL.COLID", Source: index, Filename: index, Tablename: "string",
+* Description: "string", Keywords: "string"
+*
+* @param {Object} pObject req the mapping line
+*
+* @return {Boolean} true, if import of the data was successful, otherwise false
+*/
+function iDocument(pObject)
+{
+    var resultDocument = true;
+
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    // iDocument is insert-only
+    this.setDefaultAction(pObject);
+    if(pObject.Action != "I") return resultDocument;
+
+    try
+    {
+        var desc = "";
+        if(pObject.Description != undefined) desc = this.InputRecord[pObject.Description];
+        var keyw = "";
+        if(pObject.Keywords != undefined) desc = this.InputRecord[pObject.Keywords];
+
+        if(pObject.Rowid != "" && pObject.Filename != "")
+            db.insertBinary(
+                            pObject.Tablename, 
+                            pObject.Container, 
+                            this.getOutput(pObject, "Rowid"), 
+                            null, 
+                            this.InputRecord[pObject.Source], 
+                            this.InputRecord[pObject.Filename], 
+                            desc, 
+                            keyw, 
+                            this.Config.AliasTo);
+    }
+    catch(ex)
+    {
+        logging.log(ex);
+        resultDocument = false;
+    }
+
+    return resultDocument;
+}
+
+/*
+* move import data to target
+*
+* @param {Object} pObject req the mapping line
+*
+* @example: [iMove, { Source: 3, Target: "RELATION.ADDRESS" } ]
+*
+* @return {Boolean} false, if the import of the row is not possible. otherwise true
+*/
+function iMove(pObject)
+{
+    var resultMove = true;
+
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    if(pObject.Blobfile != undefined && pObject.Blobfile == true)  // blobfile move
+    {
+        var pn = pObject.Pathname;
+        var fn = this.InputRecord[pObject.Source];
+
+        // s will be NULL is something went wrong (no file, access error, etc)
+        var s = this.getFileContent(pn.toString() + fn.toString(), util.DATA_TEXT);
+
+        // if blob file could be read, assign to output buffer,
+        // otherweise signal "no import for this row" by returning false as the function value
+        if(s != null && s != undefined)
+            this.setOutput(pObject, s);
+        else
+            resultMove = false;
+    }
+    else  // no blob file handling, just plan old move
+    {
+        var expr = "";
+        if(pObject.Source != undefined) expr = this.InputRecord[pObject.Source];
+        if(pObject.Value != undefined) expr = this.resolveSymbol(pObject, pObject.Value, pObject.Eval);
+        if(pObject.Map != undefined && pObject.Index) expr = pObject.Map[this.resolveSymbol(pObject, pObject.Index, pObject.Eval)];
+
+        //if expr is undefined, then do no replace
+        if(expr != undefined)
+        {
+            // check for trimming option
+            if(pObject.Trim != undefined && typeof(pObject.Trim) == "string")
+            {
+                switch(pObject.Trim.toLowerCase())
+                {
+                    case "left":
+                        expr = expr.replace(/^\s+/, "");
+                        break;
+                    case "right":
+                        expr = expr.replace(/\s+$/, "");
+                        break;
+                    case "both":
+                        expr = expr.replace(/^\s+|\s+$/g, "");
+                        break;
+                }
+            }
+
+            // chek for replacing option
+            if(pObject.Replace != undefined && typeof(pObject.Replace) == "string" && pObject.ReplaceTo != undefined)
+                expr = expr.replace(pObject.Replace, pObject.ReplaceTo);
+
+            // check for format conversion
+            if(pObject.HTML2Text)
+                expr = text.html2text(expr);
+            else if (pObject.RTF2Text)
+                expr = text.rtf2text(expr);
+        }
+        else
+            expr = "";
+
+        this.setOutput(pObject, expr);
+    }
+
+    return resultMove;
+}
+
+
+/*
+* Return word number "Index" from source column.
+*    Values of the mapping line:
+*    String Source the source column index
+*    String Regex the regular expression for the split
+*    Number Index the word number starting with 0
+*    String Substring "right" or "left"
+*    String Separator concatenation string, default is blank
+*
+* @param {Object} pObject req the mapping line
+*
+* @return {Boolean} true
+*/
+function iWord(pObject)
+{
+    var resultWord = true;
+
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    var mode = pObject.Substring;
+    var sep = pObject.Separator;
+    if(sep == undefined) sep = " ";  // default concat with blank
+
+    // split the input string with the regex and get the word number,
+    // negative values will count from the end of the string (e.g. -1 for the last word in a string)
+    if(pObject.Source != undefined) s = this.InputRecord[pObject.Source];
+    if(pObject.Value != undefined) s = this.resolveSymbol(pObject, pObject.Value);
+
+    s = s.split( pObject.Regex );
+    var len = s.length;
+    var num = Number(pObject.Index);
+    if(num < 0) num = len - eMath.absInt(num);
+
+    // just to be sure we are in a valid range
+    if((num >= 0) && (num < len))
+    {
+        if(mode != undefined)
+        {
+            var part = "";
+            // concatenate up the word
+            mode = mode.toString().toLowerCase();
+            if(mode == "left")
+            {
+                num++;
+                part = s.slice(0,num).join(sep);
+            }
+            else if(mode == "right")
+            {
+                part = s.slice(pObject.Index).join(sep);
+            }
+            this.setOutput(pObject, part);
+        }
+        else
+        {
+            // use the single word
+            this.setOutput(pObject, s[num]);
+        }
+    }
+
+    if(resultWord == undefined) resultWord = "";
+    return resultWord;
+}
+
+
+/*
+* return a new ID for a key field
+*			 	Value of the mapping line:
+*				String pColumn req the key column
+*
+* @param {Object} pObject req the mapping line
+*
+* @return {Boolean} true
+*/
+function iNewID(pObject)
+{
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    if (pObject.Action == undefined)
+    {
+        pObject.Action = "I";
+    }
+
+    this.setOutput(pObject, util.getNewUUID());
+    return true;
+}
+
+
+/*
+* join the list of columns into the specified target column
+* Values of the mapping line:
+*			Array pList req array containing result set indexes with joinable columns
+*			String pDelimiter req the delimiter string
+*			String pColumn req target column name
+*
+* @param {Object} pObject req the mapping line
+*
+* @example1: [iJoin, {Source: [3, 5], Delimiter: "\n", Target: "RELATION.ADDRESS"}]
+* @example2: [iJoin, {Value: ["{3}", "{5}"], Delimiter: "\n", Target: "RELATION.ADDRESS"}]
+*
+* @return {Boolean} true
+*/
+function iJoin(pObject)
+{
+    var s = "";
+    var len;
+
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    if(pObject.Source != undefined)
+        len = pObject.Source.length;
+    if(pObject.Value != undefined)
+        len = pObject.Value.length;
+
+    for(var i=0; i < len; i++)
+    {
+        if (pObject.Source != undefined)
+            if(this.InputRecord[pObject.Source[i]] != "")
+            {
+                if(i > 0 ) s += pObject.Delimiter;
+                s += this.InputRecord[pObject.Source[i]];
+            }
+
+        if(pObject.Value != undefined)
+            if(this.resolveSymbol(pObject, pObject.Value[i]) != "")
+            {
+                if(i > 0 ) s += pObject.Delimiter;
+                s += this.resolveSymbol(pObject, pObject.Value[i]);
+            }
+    }
+
+    this.setOutput(pObject, s);
+
+
+    return true;
+}
+
+/*
+* executes an sql statement with the data from input result set column in pIndex
+* Values of the mapping line:
+* Number pIndex req the index into the input result set
+* String Command req the sql command (use {0}..{n} to specify source indexes)
+* String Alias req the alias name
+* String Target req the target column
+*
+* @param {Object} pObject req the mapping line
+*
+* @return {Boolean} true
+*/
+function iSql(pObject)
+{
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    var sql = this.resolveSymbol(pObject, pObject.Command);
+
+    if (pObject.Target != undefined)
+        this.setOutput(pObject, db.cell(sql, pObject.Alias));
+    else
+        db.cell(sql, pObject.Alias);
+
+
+    return true;
+}
+
+
+/*
+* inserts or updates an relation entry
+*
+* @param {Object} pObject req the mapping line
+*
+* @example: [iInsertUpdate, { Table: "RELATION", Alias: "AO_DATEN",
+*                      			Columns: ( {Name: "RELATIONID", Source: 4, Required: true },
+*                            			     {Name: "AOTYPE", Value: "2" },
+*                                 			 {Name: "PERS_ID", Column: "PERS.PERSID" })  } ]
+*
+* @return {Boolean} true, if insert and update are successful, otherwise false
+*/
+function iInsertUpdate(pObject)
+{
+    var resultUpdate = true;
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    this.setDefaultAction(pObject);
+
+    try
+    {
+        var spalten = [];
+        var typen = [];
+        var werte = [];
+        var coldef;
+        var data_ok = true; // be optimistic ...
+        var alias = this.Config.AliasTo;
+        var tableName = this.getTableCase(pObject.Table);
+        var condition = this.resolveSymbol(pObject, pObject.Condition);
+
+        if(pObject.Action == undefined)  // set reasonable defaults for Action, if not specified
+        {
+            if(this.Config.ImportCommand == "insert")
+                pObject.Action = "I";
+            else if(this.Config.ImportCommand == "update")
+                pObject.Action = "U";
+            else if(this.Config.ImportCommand == "insert+update")
+                pObject.Action = "I+U";
+        }
+
+        if(this.Config.ImportCommand == "insert+update")
+        {
+            if(pObject.Action == "I")
+                action = "insert";
+            else if (pObject.Action == "U")
+                action = "update";
+            else if(pObject.Action == "I+U")
+            {
+                //try to find an existing entry
+                var entryid = db.cell("select count(*) from " + tableName + " where " + condition, alias);
+
+                if(Number(entryid) > 0)
+                {
+                    //exist, do update
+                    action = "update";
+                }
+                else
+                {
+                    //no entry, do insert
+                    action = "insert";
+                }
+            }
+        }
+        else if (this.Config.ImportCommand == "update")
+        {
+            action = "update";
+        }
+        else
+        {
+            action = "insert";
+        }
+
+        // loop thru the column definitions
+        for(var i=0; i < pObject.Columns.length; i++)
+        {
+            var value = undefined;
+            coldef = pObject.Columns[i];
+
+            //be sure, that no keycolumn is pushed in the array, when action like insert
+            if(coldef.Key != true || (coldef.Key == true && action == "insert")) spalten.push(this.getColumnCase(coldef.Name));
+            if(coldef.Key != true || (coldef.Key == true && action == "insert")) typen.push( this.DataType[tableName][this.getColumnCase(coldef.Name)] );
+
+            if(value == undefined && coldef.Source != undefined) value = this.InputRecord[coldef.Source];
+            if(value == undefined && coldef.Value != undefined) value = this.resolveSymbol(coldef, coldef.Value, coldef.Eval);
+            if(value == undefined && coldef.Key == true && action == "insert") value = util.getNewUUID();
+
+            //value undefined should not be pushed
+            //only add value if column was pushed
+            if(value != undefined && (coldef.Key != true || (coldef.Key == true && action == "insert"))) werte.push(value);
+
+            // do not update data if any required field is empty
+            if(coldef.Required == true && (value == undefined || value == "")) data_ok = false;
+        }
+
+        if(data_ok == true)
+        {
+            switch(action)
+            {
+                case "insert":
+                    this.insertData(tableName, spalten, typen, werte, alias);
+                    break;
+                case "update":
+                    this.updateData(tableName, spalten, typen, werte, condition, alias);
+                    break;
+            }
+        }
+    }
+    catch(ex)
+    {
+        logging.log(ex);
+        resultUpdate = false;
+    }
+
+    return resultUpdate;
+}
+
+/*
+* import a timestamp string in a specified format into a date field
+*    Values of the mapping line:
+*    String Source req the column index for the current record
+*    String Target req target column name
+*    String Format opt The timestamp format, default is YYYY-MM-DD HH:MI:SS
+*    String Timezone opt The timezone string, default is UTC
+*
+* @param {Object} pObject req the mapping line
+*
+* @return {Boolean} true, if the import of the timestamp was successfull, otherwise false
+*/
+function iTimestamp(pObject)
+{
+    var resultTimestamp = true;
+
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    var fmt = pObject.Format;
+    var tz = pObject.Timezone;
+    if(fmt == undefined || fmt == "") fmt = "yyyy-MM-dd HH:mm:ss";
+    if(tz == undefined || tz == "") tz = "UTC";
+
+    var value = "";
+    if(pObject.Source != undefined) value = this.InputRecord[pObject.Source];
+    if(pObject.Value != undefined) value = this.resolveSymbol(pObject, pObject.Value);
+    try
+    {
+        this.setOutput(pObject, datetime.toLong(value, fmt, tz));
+    }
+    catch(ex)
+    {
+        logging.log(ex);
+        resultTimestamp = false;
+    }
+
+    return resultTimestamp;
+}
+
+
+/*
+* decode an input entry by searching thru a translation list
+*    Values of the mapping line:
+*    String Value -- the input data
+*    String Target -- the target column
+*    String List -- the decode list, format: data;replacement;data;replacement.....
+*
+* @param {Object} pObject req the mapping line
+*
+* @return {Boolean} true, if the the decoding was successfull, otherwise false
+*/
+function iDecode(pObject)
+{
+    var resultDecode = true;
+
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    var wert = "";
+    if(pObject.Source != undefined) wert = this.InputRecord[pObject.Source];
+    if(pObject.Value != undefined) wert = this.resolveSymbol(pObject, pObject.Value);
+
+    var list = pObject.List;
+    var map = new Object();
+    if(list != undefined)
+    {
+        // convert decode list into map
+        list = list.split(";");
+
+        //is the list complete?
+        if(list.length % 2 == 0)
+        {
+            for(var i=0; i < list.length; i=i+2)
+            {
+                map[list[i]] = list[i+1];
+            }
+
+            // use map entry to decode
+            if(wert != "") wert = map[wert];
+
+            //if not found, set default to empty
+            if(wert == undefined) wert = "";
+        }
+        else
+        {
+            //list is not correct, so wert = "" and log error message
+            wert = "";
+            this.writeLog(this.LogLevels.Error, "[iDecode] List is not correct!");
+        }
+
+        // write to output buffer
+        this.setOutput(pObject, wert);
+    }
+    else
+    {
+        resultDecode = false;
+    }
+
+    return resultDecode;
+}
+
+
+/*
+* save an input in a globalvar
+*    Values of the mapping line:
+*    String Value -- the input data
+*    String Name -- the name for the globalvar
+*
+* @param {Object} pObject req the mapping line
+*
+* @example [(iGlobalVar {Value: "{3}", Name: "importLogin"} )     -->  $global.importLogin]
+*
+* @return {void}
+*/
+function iGlobalVar(pObject)
+{
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    var value = "";
+    var name = "";
+    if(pObject.Source != undefined) value = this.InputRecord[pObject.Source];
+    if(pObject.Value != undefined) value = this.resolveSymbol(pObject, pObject.Value);
+    if(pObject.Name != undefined) name = pObject.Name;
+    vars.getString("$global." + name, value);
+}
+
+
+
+/*
+* do character set translation.
+* basically works like iMove, but allows to specify a conversion map
+* that will be used to process the input data.
+* conversion map is a map (directionary, associative array, whatever you call it).
+* declare a varaible like theMap = new Array(); theMap("a") = "X"; theMap("b") = "z"; etc. ...
+* and specify this a sthe value for the Parameter "Map"
+*
+* Important! Usage of "Method" parameter value "replaceall" requires ADITO online 3.0.3 or above!
+*
+* Values of the mapping line:
+* String Value -- the input data
+* String Target -- the target column
+* String Map -- the decode map
+* String Method -- which Method to use: "js", "replaceall" (default to "js")]
+*
+* @param {Object} pObject req the mapping line
+*
+* @return {Boolean}
+*/
+function iCharMap(pObject)
+{
+    var resultMap = true;
+
+    //is any DoIf-condition set?
+    if (! this.doIfCheck(pObject))
+        return true;
+
+    var wert = "";
+    if(pObject.Source != undefined) wert = this.InputRecord[pObject.Source];
+    if(pObject.Value != undefined) wert = this.resolveSymbol(pObject, pObject.Value);
+
+    var map = pObject.Map;
+
+    if(map != undefined)
+    {
+        if(pObject.Method == undefined) pObject.Method = "js";  // default to JavaScript
+        this.writeLog(this.LogLevels.Debug, "[iCharMap] Using mapping method '" + pObject.Method + "' for mapping in iCharMap");
+
+        switch(pObject.Method)
+        {
+            case "js" :
+                for (var i in map)
+                {
+                    wert = wert.replace(new RegExp(i, "gi"), map[i]);
+                }
+                break;
+
+            case "replaceall" :
+                wert = text.replaceAll(wert, map);
+                break;
+        }
+
+        // write to output buffer
+        this.setOutput(pObject, wert);
+
+    }
+    else
+    {
+        this.writeLog(this.LogLevels.Warning, "[iCharMap] Map for iCharMap missing or undefined/empty");
+        resultMap = false;
+    }
+
+    return resultMap;
+}
diff --git a/process/ImporterTest_lib/ImporterTest_lib.aod b/process/ImporterTest_lib/ImporterTest_lib.aod
new file mode 100644
index 0000000000000000000000000000000000000000..e1204321106696757a47000f28e10cc890d73182
--- /dev/null
+++ b/process/ImporterTest_lib/ImporterTest_lib.aod
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
+  <name>ImporterTest_lib</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/ImporterTest_lib/process.js</process>
+  <variants>
+    <element>LIBRARY</element>
+  </variants>
+</process>
diff --git a/process/ImporterTest_lib/process.js b/process/ImporterTest_lib/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..342d3997fd4c9de7c26706f028f61cb8f6ac1626
--- /dev/null
+++ b/process/ImporterTest_lib/process.js
@@ -0,0 +1,74 @@
+import("system.question");
+import("system.text");
+import("system.db");
+import("Importer_lib");
+
+function ImporterTest() {
+    this.startImporter = function (pTestCases) {
+        var parse = function (pElement, pPath) {
+            if (pElement.childs == undefined) {
+                return [{ 
+                    id: text.encodeMS(pPath.concat(pElement.name)), 
+                    element: pElement }];
+            }
+            var childs = [];
+            for (let i = 0; i < pElement.childs.length; i++) {
+                childs.push(parse(pElement.childs[i], pPath.concat(pElement.name))[0])
+            }
+            return [{
+                id: text.encodeMS(pPath.concat(pElement.name)),
+                element: {
+                    name: pElement.name,
+                    description: pElement.description,
+                    childs: childs.map(function (val) { return val.id })
+                }
+            }].concat(childs);
+        }.bind(this)
+
+        for (let i = 0 ; i < Object.keys(pTestCases).length; i++)  {
+            var res = parse(pTestCases[Object.keys(pTestCases)[i]], []);
+            if (res.length == 1) {
+                this.startProcess(res[0].element);
+            } else {
+                for (var j = 1; j< res.length; j++) {
+                    var testcase = res[j].element;
+                    this.startProcess(testcase);
+                }
+            }
+        }
+    }
+    
+    this.getBaseImporter = function (pConfig) {
+        var imp = new Importer(pConfig);
+        imp.Log = "CONSOLE";
+        imp.LogLevel = imp.LogLevels.Debug;
+        imp.ImportUser = "IMPORTER_TESTER";
+        imp.Preview = false;
+        imp.Debug = true;
+        imp.LogLevel = imp.LogLevels.Debug;
+        imp.BatchSize = 50;
+        imp.skipEmptyValue = false;
+        if (db.getDatabaseType(pConfig.AliasTo) == db.DBTYPE_MARIADB10) imp.TableCase = imp.Cases.Lower;
+        return imp;
+    }
+    
+    this.startProcess = function (testCase) {
+        if (testCase.fn != undefined)  {   
+            var cbFn = function (pConfig) {
+                return this.getBaseImporter(pConfig)
+            }.bind(this)
+
+            var testResult = testCase.fn.call(testCase, cbFn);
+            if (testResult instanceof Importer)  {
+                testResult = testResult.process();
+                question.showMessage("Successful: " + testCase.name +"\n" + JSON.stringify(testResult, null, " "))
+            } else {
+                question.showMessage("Successful\nResult:\n\n" + testResult)
+            }
+        } else {
+            question.showMessage("No test function defined!", question.WARNING)
+        }
+        return true;
+    }
+}
+
diff --git a/process/Importer_lib/Importer_lib.aod b/process/Importer_lib/Importer_lib.aod
new file mode 100644
index 0000000000000000000000000000000000000000..0c29a1e97e07715224d80de8a9a3a4f803a5fcf0
--- /dev/null
+++ b/process/Importer_lib/Importer_lib.aod
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
+  <name>Importer_lib</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/Importer_lib/process.js</process>
+  <variants>
+    <element>LIBRARY</element>
+  </variants>
+</process>
diff --git a/process/Importer_lib/process.js b/process/Importer_lib/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..a693e85cf7ed62c81e6dfed7f74471cd8b759a70
--- /dev/null
+++ b/process/Importer_lib/process.js
@@ -0,0 +1,1553 @@
+import("system.db");
+import("system.vars");
+import("system.translate");
+import("system.logging");
+import("system.datetime");
+import("system.fileIO");
+import("system.swing");
+import("system.util");
+import("system.text");
+import("ErrorHandling_lib");
+import("ImporterCustomMappingFunctions_lib");
+import("ImporterMappingFunctions_lib");
+
+/////////////////////////////////////////////////////////////////////
+/// importer module constructor function                        ///
+/// DO NOT TOUCH - use lib_importerCustomMappingFunctions       ///
+///////////////////////////////////////////////////////////////////
+/*
+* the importer class, main object
+* @param {String []} pConfig req ( Name, FunktionsArt, Details )
+*
+* @version 2.0
+*
+* @return {void}
+*/
+function Importer(pConfig)
+{
+    var batchNum = 0;
+    var batchStart = 0;
+    var batchList;
+    var mappingtimer = [];
+    var exectimer = {
+        insertData: 0,
+        updateData: 0,
+        outbuffer: 0,
+        logging: 0,
+        getdata: 0,
+        updateDecisionTime: 0,
+        insertUpdateActions: 0,
+        dbDataModifiedTime: 0,
+        dbInsertTime: 0,
+        dbUpdateTime: 0,
+        dataLoopTime: 0,
+        mapPerRecordTime: 0,
+        outputPerRecordTime: 0
+    };
+    var startTime = datetime.date();//this will be overwriten when starting the importing-process
+    var stopTime;
+    var logBuffer = [];
+    this.insertArray = [];
+    this.updateArray = [];
+
+    this.LogLevels = {
+        Minimal: 0,
+        Error : 1,
+        Warning : 2,
+        Info : 3,
+        Debug : 4,
+        Preview : 5
+    };
+
+    this.Cases = {
+        Lower: 0,
+        Upper: 1,
+        Unchanged: 2
+    };
+
+    this.Process = "";
+    this.Config = pConfig;
+    this.BatchSize = 50;
+    this.MaxRows = 0;
+    this.Preview = false;
+    this.Debug = false;
+    this.Log = "CONSOLE";
+    this.fileInputCharset = "UTF8";
+    this.LogLevel = this.LogLevels.Warning;
+    this.enableLogBuffer = false;
+    this.InputRecord = new Array();
+    this.OutputRecord = new Object();
+    this.ImportUser = "IMPORTER";
+    this.FuncBuffer = new Object();
+    this.CompleteUpdate = true;
+    this.ErrorLog = "";
+    this.DataType = null;
+    this.KeyColumn = new Object();  // contains key info as in KeyColumn["tbl.col"] = "I" | "U" | "I+U"
+    this.UseAOType = true; //default true, so that AOType will be uses (for older systems)
+    this.UseUUID = true; //default false, so that db.getNewID() instead of util.getNewUUID() would be uses
+    this.TableCase = this.Cases.Upper; //default is uppercase
+    this.ColumnCase = this.Cases.Upper; //default is uppercase
+    this.skipEmptyValue = true;
+
+    this.AttributeCache = undefined;
+    this.useAttributeCache = false;
+    this.attributeCacheLoadedSuccessfully = false;
+    this.loadAttributeCache = function(){
+        this.writeLog(this.LogLevels.Info, "[LoadAttributeCache]loading Attibute cache");
+        try
+        {
+            this.AttributeCache = {};
+            let TMP = 0;
+            var cD = {
+                 attrNameL1: TMP
+                ,attrNameL2: ++TMP
+                ,attrNameL3: ++TMP
+                ,attrIdL1: ++TMP
+                ,attrIdL2: ++TMP
+                ,attrIdL3: ++TMP
+                ,attrObjectL1: ++TMP
+                ,attrObjectL2: ++TMP
+                ,attrCompL1: ++TMP
+                ,attrCompL2: ++TMP
+                ,attrSqlIdFieldL1: ++TMP
+                ,attrSqlDisplayFieldL1: ++TMP
+                ,attrSqlFromFieldL1: ++TMP
+                ,attrSqlWhereFieldL1: ++TMP
+                ,attrSqlIdFieldL2: ++TMP
+                ,attrSqlDisplayFieldL2: ++TMP
+                ,attrSqlFromFieldL2: ++TMP
+                ,attrSqlWhereFieldL2: ++TMP
+            };
+
+            var sqlStr = "select "
+            + this.getColumnCase( "A.ATTRNAME, B.ATTRNAME, C.ATTRNAME "
+                + ",A.ATTRID, B.ATTRID, C.ATTRID "
+                + ",AO_A.ATTROBJECT, AO_B.ATTROBJECT "
+                + ",A.ATTRCOMPONENT, B.ATTRCOMPONENT "
+                + ",A.SQLIDCOLUMN, A.SQLSHOWCOLUMN, A.SQLFROMDEF, A.SQLWHERE "
+                + ",B.SQLIDCOLUMN, B.SQLSHOWCOLUMN, B.SQLFROMDEF, B.SQLWHERE "
+                + "")
+            + " from " + this.getTableCase("ATTR A")
+            + " left join " + this.getTableCase("ATTR B") + " on " + this.getColumnCase("A.ATTRID") + " = " + this.getColumnCase("B.ATTR_ID")
+            + " left join " + this.getTableCase("ATTR C") + " on " + this.getColumnCase("B.ATTRID") + " = " + this.getColumnCase("C.ATTR_ID")
+            + " left join " + this.getTableCase("ATTROBJECT AO_A") + " on " + this.getColumnCase("AO_A.ATTR_ID") + " = " + this.getColumnCase("A.ATTRID")
+            + " left join " + this.getTableCase("ATTROBJECT AO_B") + " on " + this.getColumnCase("AO_B.ATTR_ID") + " = " + this.getColumnCase("B.ATTRID")
+            + " where " + this.getColumnCase("A.ATTR_ID") + " is null "//if it has an attr_id it's level 2 an with that ATTR B (or ATTR C)
+            + "";
+            this.writeLog(this.LogLevels.Info, "[LoadAttributeCache]executed select is: " + sqlStr);
+            var data = db.table(sqlStr, this.Config.AliasTo);
+            var attributeTree = {};
+            for(var i = 0, j = data.length; i < j; i++)
+            {
+                let row = data[i];
+                if (attributeTree[row[cD.attrNameL1]] == undefined)
+                    attributeTree[row[cD.attrNameL1]] = {
+                         id: row[cD.attrIdL1]
+                        ,component: row[cD.attrCompL1]
+                        ,usedInFrames: {}
+                        ,children: {}
+                    };
+
+                if (row[cD.attrObjectL1] != "")
+                    attributeTree[row[cD.attrNameL1]].usedInFrames[row[cD.attrObjectL1]] = true;
+                else
+                    this.writeLog(this.LogLevels.Warning, '[loadAttributeCache]attribute "' + row[cD.attrNameL1] + '" has no ATTROBJECT');
+                //selectCombo, init for hybrid-cache (loaded once, when attribute is really needed):
+                if (row[cD.attrCompL1] == "7"
+                 && attributeTree[row[cD.attrNameL1]].loadedSelectCombo == undefined)
+                {
+                    attributeTree[row[cD.attrNameL1]].loadedSelectCombo = false;
+                    attributeTree[row[cD.attrNameL1]].selectComboSql = "select " + row[cD.attrSqlIdFieldL1]
+                        + " ," + row[cD.attrSqlDisplayFieldL1]
+                        + " from " + row[cD.attrSqlFromFieldL1]
+                        + (row[cD.attrSqlWhereFieldL1] != "" ? " where " : "") + row[cD.attrSqlWhereFieldL1];
+                }
+
+
+                if (row[cD.attrNameL2] != "")
+                {
+                    if (attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]] == undefined)
+                        attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]] = {
+                             id: row[cD.attrIdL2]
+                            ,component: row[cD.attrCompL2]
+                            ,usedInFrames: {}
+                            ,children: {}
+                        };
+
+                    if (row[cD.attrObjectL2] != "")
+                        attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].usedInFrames[row[cD.attrObjectL2]] = true;
+                    else
+                        this.writeLog(this.LogLevels.Warning, '[loadAttributeCache]attribute "' + row[cD.attrNameL1] + "|" + row[cD.attrNameL2] + '" has no ATTROBJECT');
+
+                    //selectCombo, init for hybrid-cache:
+                    if (row[cD.attrCompL2] == "7"
+                        && attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].loadedSelectCombo == undefined)
+                    {
+                        attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].loadedSelectCombo = false;
+                        attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].selectComboSql = "select " + row[cD.attrSqlIdFieldL2]
+                        + " ," + row[cD.attrSqlDisplayFieldL2]
+                        + " from " + row[cD.attrSqlFromFieldL2]
+                        + (row[cD.attrSqlWhereFieldL2] != "" ? " where " : "") + row[cD.attrSqlWhereFieldL2];
+                    }
+
+                    //this nomally happens only if component is combo
+                    if (row[cD.attrNameL3] != "")
+                    {
+                        if (attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].children[row[cD.attrNameL3]] == undefined)
+                            attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].children[row[cD.attrNameL3]] = {
+                                 id: row[cD.attrIdL3]
+                            };
+                    }
+                }
+//                        //jetzt muss geprüft werden ob sich die ATTRIDs unterscheiden, wenn ja bedeutet dass, das ein Attributname mehrfach vergeben wurde
+//                        //dann darf keiner der Daten in den Cache geladen werden, dass würde nur zu fehlerhaften daten führen
+//                        var existingAttrid = this.AttributeCache[ attrname ].ATTRID;
+//                        if( existingAttrid != attrid )
+//                        {
+//                            this.AttributeCache[ attrname ] = null; //cache sicherheitshalber erst freigeben
+//                            this.AttributeCache[ attrname ] = undefined;
+//                            this.writeLog(this.LogLevels.Error, "[LoadAttributeCache] Multiple Attributes were found for attribute: '" + attrname + "'."
+//                                + "\r\nNo Attribute with attrname '" + attrname + "' will be loaded into the cache. ");
+//                        }
+            }
+            this.AttributeCache = attributeTree;
+            this.attributeCacheLoadedSuccessfully = true;
+        }
+        catch(ex)
+        {
+            this.attributeCacheLoadedSuccessfully = false;
+            this.writeLog(this.LogLevels.Error, "[LoadAttributeCache]Error while loading Attributes:\r\n" + logging.toLogString(ex) );
+        }
+        if( this.attributeCacheLoadedSuccessfully )
+            this.writeLog(this.LogLevels.Info, "[LoadAttributeCache]Loaded Attibute cache successfully");
+        else
+            this.writeLog(this.LogLevels.Info, "[LoadAttributeCache]Failed loading Attibute cache");
+    }
+
+    this.recordCounts = {//is initialized with numbers in the this.process-function
+         total: null
+        ,skip: null
+        ,update: null
+        ,insert: null
+        ,unchanged: null
+    };
+    Object.defineProperty(this.recordCounts, 'unchanged', {
+        //"this" is here the passed object
+         get: function (){ return (this.total - this.insert - this.update - this.skip)}
+        ,set: function (){throw new Error("importers recordCounts cannot be set; it is read-only")}
+    });
+
+    this.process = function()
+    {
+        this.mappingLength = this.Config.Mapping.length;
+        var i;
+        var t;
+        var pkid;
+        var c;
+        var cond;
+        var s;
+
+        var pImporter = this;
+
+        this.recordCounts.total = 0;
+        this.recordCounts.skip = 0;
+        this.recordCounts.insert = 0;
+        this.recordCounts.update = 0;
+        
+        // Infoobject for Callbackfunction
+        var recordStack = {
+            sourceDataSet: [],
+            insertUpdate: "",
+            insertData: {
+                successful: true, 
+                array: []
+            },
+            updateData: {
+                successful: true, 
+                array: []
+            },
+            exception:  [],
+            pId: ""
+        };
+
+        var daten;
+        var skip;
+        var headercount = 0;
+        var batchlist = [];
+        var idcolumn;
+        var importtype = "S";        // F,S or M for "file", "single" or "multi" batch, default to single batch
+        batchStart = 0;              // current batch start index
+        var logstring = "";          // contains the log file, if not writing to server log
+        var requiredTargets = new Object();   // stores the required target columns
+        // prepare logging stuff
+        if(this.Log != "" && this.Log != undefined && this.Log.split(".")[0] == "$global") vars.set(this.Log, "");
+        if(this.Preview == true) this.LogLevel = this.LogLevels.Preview;
+
+        if (this.enableLogBuffer)
+            logBuffer = [];
+        else
+            logBuffer = ["logBuffer is not enabled; you cannot use getLogMessages(...)"];
+        startTime = datetime.date();
+        s = datetime.toDate(startTime, translate.text("yyyy-MM-dd HH:mm:ss"), "UTC");
+        this.writeLog(this.LogLevels.Minimal, "Started at " + s + " (UTC)");
+        logging.log("[IMPORT] Start at " + s + " UTC");
+        if(this.Preview) this.writeLog(this.LogLevels.Info, "======== import running in preview mode ========");
+        if (this.useAttributeCache)
+            this.loadAttributeCache();
+        this.writeLog(this.LogLevels.Debug, "Building mapping tables.");
+        // get all column type from all tables of the target alias
+        this.DataType = this.getDataTypes(this.Config.AliasTo);
+        // build an object with the data types for each column and
+        // check for key fields and collect them in parmObject.KeyRecord
+        for(i=0; i < this.mappingLength; i++)
+        {
+            mappingtimer[i] = 0;  // init mapping timer
+
+            var target = this.Config.Mapping[i][1]["Target"];
+            if(target != undefined && target.substr(0,4).toLowerCase() != "var.")  // exclude var storage
+            {
+                // collect a required target, if we find one (these columns must not be empty)
+                requiredTargets[target] = (this.Config.Mapping[i][1]["Required"] == true);
+                // collect all key columns in an object array
+                // KeyColumn[table ][column] = action1;action2...
+                var coldata = target.split(".");
+
+                if(this.Config.Mapping[i][1]["Key"] != undefined)  //2009-05-13  TR  changed, because on insertcommand "insert" or "update" there can be no Action
+                {
+                    if(this.KeyColumn[ this.getTableCase(coldata[0]) ] == undefined) this.KeyColumn[ this.getTableCase(coldata[0]) ] = new Object();
+                    if(this.KeyColumn[ this.getTableCase(coldata[0]) ][ this.getColumnCase(coldata[1]) ] == undefined) this.KeyColumn[ this.getTableCase(coldata[0]) ][ this.getColumnCase(coldata[1]) ] = "key";
+
+                    this.setDefaultAction(this.Config.Mapping[i][1]);
+
+                    this.KeyColumn[ this.getTableCase(coldata[0]) ][ this.getColumnCase(coldata[1]) ] = this.KeyColumn[ this.getTableCase(coldata[0]) ][ this.getColumnCase(coldata[1]) ] + ";" + this.Config.Mapping[i][1]["Action"];
+                }
+            }
+        }
+
+        batchNum = 0;
+        headercount = this.Config.HeaderLines;
+        if(headercount == undefined) headercount = 0;
+        this.writeLog(this.LogLevels.Warning, "Beginning data import.");
+        // main loop
+        while(daten = this.getNextBatch(this.Config), daten != null )
+        {
+            var dataLoopTimeStart = datetime.date();
+            this.writeLog(this.LogLevels.Info, "Processing batch #" + batchNum);
+
+            // import each row of the current batch
+            for(var bi=0; bi < daten.length; bi++)
+            {
+                recordStack.exception ="";
+                recordStack.sourceDataSet = daten[bi];
+                recordStack.insertData  = {
+                    successful: true, 
+                    array: []
+                }
+                recordStack.updateData  = {
+                    successful: true, 
+                    array: []
+                }
+
+                // skip empty data rows
+                if(daten[bi].length == 1 && daten[bi][0] == "")
+                {
+                    if(this.Debug == true) {
+                        this.writeLog(this.LogLevels.Debug, "Skipping empty row " + bi);
+                    }
+                    continue;
+                }
+
+                this.recordCounts.total++;
+                skip = false;
+
+                // skip header rows (usually file import only)
+                if(bi < headercount)
+                {
+                    if(this.Preview == true && this.Debug == true)
+                    {
+                        this.writeLog(this.LogLevels.Debug, "Skipping header row " + bi);
+                    }
+                    continue;
+                }
+                if (this.Debug)
+                    this.writeLog(this.LogLevels.Debug, "Row " + bi);
+
+                this.InputRecord = daten[bi];       // assign input fields for this record
+                this.OutputRecord = new Object();   // clear output fields for this record
+
+                var mapRecordMapTimeStart = datetime.date();
+                // call mapping functions
+                for(i=0, ml = this.mappingLength; i < ml; i++)
+                {
+                    try
+                    {
+                        var fname = this.Config.Mapping[i][0].name;//name is inherited function-property
+
+                        if (this.Debug)
+                            this.writeLog(this.LogLevels.Debug, "Calling mapping function " + fname);
+                        // call mapping function and time it
+                        t = datetime.date();
+                        var mapresult = this.Config.Mapping[i][0].call(this, this.Config.Mapping[i][1]);
+                        t = datetime.date() - t;
+                        mappingtimer[i] += t;
+                        // if the mapping did not succeed, log function and ID value
+                        if(mapresult == false)
+                        {
+                            this.writeLog(this.LogLevels.Error, "Mapping function " + fname + " failed for row " + this.recordCounts.total);
+                            recordStack.exception = "Fehler Zeile: " + this.recordCounts.total + " - Info:  Mapping function " + fname + " failed for row " + this.recordCounts.total + " Column: " + this.Config.Mapping[i][1]["Target"]; 
+                            skip = true;
+                        }
+                    }
+                    catch(ex)
+                    {
+                        logging.log(ex["rhinoException"] != undefined ? ex["rhinoException"] : ex)
+                        this.writeLog(this.LogLevels.Error, "Exception in mapping function [" + fname + "] for input row " + this.recordCounts.total+ " - " + this.Config.Mapping[i][1]["Target"]);                       
+                        recordStack.exception = errorHandling.getClearMessage(ex)+  " -  Column: " + this.Config.Mapping[i][1]["Target"];
+                        skip = true;
+
+                    }
+                }
+                exectimer.mapPerRecordTime += datetime.date() - mapRecordMapTimeStart;
+
+
+                if(skip == false)  // do we continue with this record?
+                {
+                    // if a debug callback function has been defined, call it
+                    if(this.DebugCallback != null && typeof(this.DebugCallback) == "function")
+                    {
+                        this.DebugCallback(this.OutputRecord);
+                    }
+
+
+                    var tt = datetime.date();
+
+                    var outdata = new Object();
+
+                    for(var tbl in this.OutputRecord)  // loop thru all output tables
+                    {
+                        for(var col in this.OutputRecord[ tbl ])  // loop thru all output columns
+                        {
+                            if(tbl.toLowerCase() != "var")   // exclude var storage
+                            {
+                                for(var action in this.OutputRecord[tbl][col])
+                                {
+                                    if(outdata[tbl] == undefined) outdata[tbl] = "";
+                                    outdata[tbl] = outdata[tbl] + ";" + action;
+
+                                    // check for required data mappings and set skip again, we check this later
+                                    skip = (requiredTargets[tbl + "." + col] == true && (this.OutputRecord[tbl][col][action] == undefined || this.OutputRecord[tbl][col][action] == ""));
+                                }
+                            }
+                        }
+                    }
+
+                    tt = datetime.date() - tt;
+                    exectimer["outbuffer"] += tt;
+
+                    var outputRecordTimeStart = datetime.date();
+                    // loop thru all distinct tables, maybe we have more than one record to write
+                    for(t in this.OutputRecord)
+                    {
+                        var spalten = [];
+                        var typen = [];
+                        var werte = [];
+
+                        // we do insert-for-new and update-for-existing
+                        if(skip == false && this.Config.ImportCommand == "insert+update")
+                        {
+                            var insertUpdateStart = datetime.date();
+                            pkid = "0";  // assume we do not find an existing record
+
+                            // if no key values have been specified, there is no need to check
+                            // so we do an insert
+                            if(this.KeyColumn[t] != undefined )
+                            {
+                                // check for existing data row based on key values
+                                cond = this.generateKeyCondition(t);
+                                var sql = "select count(*) from " + t + " where " + cond;
+                                this.writeLog(this.LogLevels.Debug, "Insert/Update decision for table " + t + " based on: " + cond);
+                                pkid = db.cell(sql, this.Config.AliasTo);
+                            }
+                            exectimer["updateDecisionTime"] += (datetime.date() - insertUpdateStart);
+
+                            if(t.toLowerCase() != "var")
+                            {
+                                if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
+                                else
+                                {
+                                    for(c in this.OutputRecord[t])
+                                    {
+                                        if(this.OutputRecord[t][c]["I+U"] != undefined && (this.OutputRecord[t][c]["I+U"] != "" || !this.skipEmptyValue) && this.OutputRecord[t][c]["I+U"] != null)
+                                        {
+                                            if(this.DataType[t][c] != undefined)
+                                            {
+                                                spalten.push(c);
+                                                typen.push(this.DataType[t][c]);
+                                                werte.push(this.OutputRecord[t][c]["I+U"]);
+                                            }
+                                            else
+                                                this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
+                                        }
+                                    }
+                                }
+                            }
+                            if(pkid != "0")  // exists
+                            {
+                                if(t.toLowerCase() != "var")
+                                {
+                                    if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
+                                    else
+                                    {
+                                        for(c in this.OutputRecord[t])
+                                        {
+                                            if(this.OutputRecord[t][c]["U"] != undefined && (this.OutputRecord[t][c]["U"] != "" || !this.skipEmptyValue) && this.OutputRecord[t][c]["U"] != null
+                                                && this.OutputRecord[t][c]["I+U"] == undefined)
+                                                {
+                                                if(this.DataType[t][c] != undefined)
+                                                {
+                                                    spalten.push(c);
+                                                    typen.push(this.DataType[t][c]);
+                                                    werte.push(this.OutputRecord[t][c]["U"]);
+                                                }
+                                                else
+                                                    this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if(!this.updateData(t, spalten, typen, werte, cond, this.Config.AliasTo)) skip = true;
+                            }
+                            else
+                            {
+                                if(t.toLowerCase() != "var")
+                                {
+                                    if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
+                                    else
+                                    {
+                                        for(c in this.OutputRecord[t])
+                                        {
+                                            if(this.OutputRecord[t][c]["I"] != undefined && this.OutputRecord[t][c]["I"] != "" && this.OutputRecord[t][c]["I"] != null
+                                                && this.OutputRecord[t][c]["I+U"] == undefined)
+                                                {
+                                                if(this.DataType[t][c] != undefined)
+                                                {
+                                                    spalten.push(c);
+                                                    typen.push(this.DataType[t][c]);
+                                                    werte.push(this.OutputRecord[t][c]["I"]);
+                                                }
+                                                else
+                                                    this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if(!this.insertData(t, spalten, typen, werte, this.Config.AliasTo)) skip = true;
+                            }
+                            exectimer["insertUpdateActions"] += (datetime.date() - insertUpdateStart);
+                        }
+
+                        // we do an insert
+                        if(skip == false && this.Config.ImportCommand == "insert")
+                        {
+                            if(t.toLowerCase() != "var")
+                            {
+                                if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
+                                else
+                                {
+                                    for(c in this.OutputRecord[t])
+                                    {
+                                        if(this.DataType[t][c] != undefined)
+                                        {
+                                            spalten.push(c);
+                                            typen.push(this.DataType[t][c]);
+                                            werte.push(this.OutputRecord[t][c]["I"]);
+                                        }
+                                        else
+                                            this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
+                                    }
+                                }
+                            }
+                            if(!this.insertData(t, spalten, typen, werte, this.Config.AliasTo)) skip = true;
+                        }
+
+                        // we do an update?
+                        if(skip == false && this.Config.ImportCommand == "update")
+                        {
+                            if(t.toLowerCase() != "var")
+                            {
+                                if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
+                                else
+                                {
+                                    for(c in this.OutputRecord[t])
+                                    {
+                                        if(this.DataType[t][c] != undefined)
+                                        {
+                                            spalten.push(c);
+                                            typen.push(this.DataType[t][c]);
+                                            werte.push(this.OutputRecord[t][c]["U"]);
+                                        }
+                                        else
+                                            this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
+                                    }
+                                }
+                            }
+
+                            // create condition by looking for key fields
+                            cond = this.generateKeyCondition(t);
+                            if(!this.updateData(t, spalten, typen, werte, cond, this.Config.AliasTo)) skip = true;
+                        }
+                    }
+                    exectimer.outputPerRecordTime += datetime.date() - outputRecordTimeStart;
+                }
+
+                // this is *not* the else-branch for the if above, because we may change the value for "skip"
+                // even it was false when comparing in the "if" above. so we check this here in a separate
+                // if to get all occurrences of skipped import rows.
+                if(skip == true) this.recordCounts.skip++;
+                //Now insert/update all Columns
+                if(!skip)
+                {
+                    var totalDBexecTime = datetime.date();
+                    let insertSuccessfull = true;
+                    let updateSuccessfull = true;
+                    try
+                    {
+                        if(this.insertArray.length > 0)
+                        {
+                            insertSuccessfull = false;
+                            var startInsert = datetime.date();
+                            db.inserts(this.insertArray, this.Config.AliasTo);
+                            exectimer.dbInsertTime += datetime.date() - startInsert;
+                            this.recordCounts.insert++;
+                            insertSuccessfull = true;
+                             // insert
+                            recordStack.insertUpdate = "insert";
+                            recordStack.insertData  = {
+                                successful: true, 
+                                array: this.insertArray
+                            };
+                        }
+                        if(this.updateArray.length > 0)
+                        {
+                            updateSuccessfull = false;
+                            var startUpdate = datetime.date();
+                            db.updates(this.updateArray, this.Config.AliasTo);
+                            exectimer.dbUpdateTime += datetime.date() - startUpdate;
+                            this.recordCounts.update++;
+                            updateSuccessfull = true;
+                            // update
+                            recordStack.insertUpdate = "update";
+                            recordStack.updateData  = {
+                                successful: true, 
+                                array: this.updateArray
+                            };
+                        }
+                    }
+                    catch(ex)
+                    {
+                        this.writeLog(this.LogLevels.Error, "Error at " + (insertSuccessfull ? "" : "Insert") + (updateSuccessfull ? "": "Update") + ":" + logging.toLogString(ex));
+                        logging.log(ex["rhinoException"] != undefined ? ex["rhinoException"] : ex)
+                        this.recordCounts.skip++;
+                        recordStack.exception = errorHandling.getClearMessage(ex);
+                        
+                        if(this.insertArray.length > 0 && !insertSuccessfull) {
+                            this.writeLog(this.LogLevels.Info, "Insert array: " + JSON.stringify(this.insertArray, null, " "));
+                            recordStack.insertData  = {
+                                successful: false, 
+                                array: this.updateArray
+                            };
+                        }
+                        if(this.updateArray.length > 0 && !updateSuccessfull) {
+                            this.writeLog(this.LogLevels.Info, "Update array: " + JSON.stringify(this.updateArray, null, " "));
+                            recordStack.updateData  = {
+                                successful: false, 
+                                array: this.updateArray
+                            };
+                        }
+                    }
+
+                    exectimer.dbDataModifiedTime += datetime.date() - totalDBexecTime;
+
+                    //Clear Arrays
+                    this.insertArray = [];
+                    this.updateArray = [];
+                }
+                // if a progress callback function has been defined, call it
+                if(this.ProgressCallback != null && typeof(this.ProgressCallback) == "function")
+                {
+                    this.ProgressCallback(skip, bi, recordStack, this.Config.AliasFrom, this.Config.Callback.TableFrom, this.Config.Callback.IDColumn, this.Config.Callback.IDColumnIndex);
+                }
+
+                if(this.MaxRows > 0 && this.recordCounts.total >= this.MaxRows)
+                {
+                    break;
+                }
+            }  // end for (row of current batch)
+            if(this.MaxRows > 0 && this.recordCounts.total >= this.MaxRows)
+            {
+                break;
+            }
+            exectimer.dataLoopTime += datetime.date() - dataLoopTimeStart;
+        }
+
+        this.showCounts();
+        stopTime = datetime.date();
+        this.showTimings();
+        s = datetime.toDate(stopTime, translate.text("yyyy-MM-dd HH:mm:ss"), "UTC");
+        this.writeLog(this.LogLevels.Minimal, "End at " + s + " UTC");
+        logging.log("[IMPORT] End at " + s + " UTC");
+
+        return {
+             totalCount: this.recordCounts.total
+            ,skipCount: this.recordCounts.skip
+            ,insertCount: this.recordCounts.insert
+            ,updateCount: this.recordCounts.update
+            ,unchangedCount: this.recordCounts.unchanged
+        };
+    }
+    //	show timing information
+    this.showTimings = function(pPrefix)
+    {
+        if (pPrefix == undefined)
+            pPrefix = "";
+
+        var mappingtotal = 0;
+        for(var i=0; i < this.Config.Mapping.length; i++)
+        {
+            mappingtotal += mappingtimer[i];
+        }
+        if (stopTime == undefined)
+            this.writeLog(this.LogLevels.Warning, pPrefix + "Total run time till now: " + ((datetime.date() - startTime) / 1000) + " seconds.");
+        else
+            this.writeLog(this.LogLevels.Warning, pPrefix + "Total run time: " + ((stopTime - startTime) / 1000) + " seconds.");
+
+        for(var k in exectimer)
+        {
+            this.writeLog(this.LogLevels.Info, pPrefix + "Total " + k + " time: " + (exectimer[k] == "0" ? "<1" : exectimer[k]) + " ms ");
+        }
+
+        this.writeLog(this.LogLevels.Warning, pPrefix + "Total map time: " + (mappingtotal / 1000) + " seconds.");
+
+        for(i=0; i < this.Config.Mapping.length; i++)
+        {
+            var fname =  this.Config.Mapping[i][0].name;//name is inherited function-property
+            this.writeLog(this.LogLevels.Info, pPrefix + "Mapping #" + i + " " + fname + ": " + (mappingtimer[i] == "0" ? "<1" : mappingtimer[i]) + " ms ");
+        }
+    }
+
+    this.showCounts = function(pPrefix)
+    {
+        if (pPrefix == undefined)
+            pPrefix = "";
+
+        this.writeLog(this.LogLevels.Minimal, pPrefix + "Total: " + this.recordCounts.total
+                    + ", Skipped: " + this.recordCounts.skip
+                    + ", Inserted: " + this.recordCounts.insert
+                    + ", Updated: " + this.recordCounts.update
+                    + ", Unchanged: " +  this.recordCounts.unchanged );
+    }
+
+    //		this function yields the next batch of data to import.
+    this.getNextBatch = function(pConfig){
+        var tt = datetime.date();  // exec timer
+        var resultBatch;
+        var bs;
+        var daten;
+
+        this.writeLog(this.LogLevels.Debug, "Executing getNextBatch()");
+        batchNum++;
+
+        var impmode;
+
+        if(pConfig.DataFunction != undefined)
+            impmode = "array";
+        else if(pConfig.DataFile != undefined)
+        {
+            if(pConfig.DataFile.substr(pConfig.DataFile.length-3, pConfig.DataFile.length) == 'xml') //is the file an xml file? then use xml-mode
+                impmode = "xml";
+            else
+                impmode = "file";
+        }
+        else if(pConfig.XMLObject != undefined)
+            impmode = "xml";
+        else
+            impmode = "table";
+
+
+        switch(impmode)
+        {
+            case "xml":
+                if(batchNum > 1) return null;	// we read files in one sweep
+
+                if(this.Debug == true) this.writeLog("Getting input xml " + pConfig.DataFile, false, 3);
+                // file import, which is always a single batch operation
+                importtype = "F";
+                var strXML;
+
+                // get xml-string directly or load from file?
+                if(pConfig.DataFile != "")
+                    strXML =	this.getFileContent(pConfig.DataFile, util.DATA_TEXT); //load from file
+                else
+                    strXML = pConfig.XMLObject; //get xml-string
+
+                if(strXML != "" && strXML != undefined)
+                {
+                    var xmlData = new XML(strXML);
+                    resultBatch = [];
+                    var xmlArr = [];
+
+                    //get every row-element
+                    for each(xmlItem in xmlData.data.row)
+                    {
+                        xmlArr = [];
+
+                        for each(xmlItem2 in xmlItem.column)
+                            xmlArr.push(xmlItem2);
+
+                        resultBatch.push(xmlArr);
+                    }
+                }
+                else
+                    this.writeLog("XML-Data is empty or undefined!", false, 0);
+
+                return resultBatch;
+                break;
+
+            case "file":
+                if(batchNum > 1) return null;	// we read files in one sweep
+                if(this.Debug == true) this.writeLog("Reading input file " + pConfig.DataFile, false, 3);
+                // file import, which is always a single batch operation
+                importtype = "F";
+
+                // otherwise, load the file
+                var filestring = this.getFileContent(pConfig.DataFile, util.DATA_TEXT);
+                var rs = pConfig.RowSeparator;
+                if(rs == undefined) rs = "\n";
+                var cs = pConfig.ColumnSeparator;
+                if(cs == undefined) cs = ",";
+                var sd = pConfig.StringDelimiter;
+                if(sd == undefined) sd = "";
+
+                resultBatch = text.parseCSV(filestring, rs, cs, sd);
+                tt = datetime.date() - tt;
+                exectimer["getdata"] += tt;
+                return resultBatch;
+                break;
+
+            case "array":
+                bs = this.BatchSize;
+                if(bs == undefined) bs = 0;
+
+                resultBatch = this.Config.DataFunction.call(this, batchNum, bs);
+                tt = datetime.date() - tt;
+                exectimer["getdata"] += tt;
+                return resultBatch;
+                break;
+
+            case "table":
+                // table import
+                bs = this.BatchSize;
+                if(bs == undefined)
+                    bs = 0;
+
+                //bs specified: read data in blocks to save memory
+                if(bs > 0 )
+                {
+                    if(this.Config.IdQuery == undefined || this.Config.IdColumn == undefined)
+                    {
+                        //new method for batch-processsing (paging)
+                        daten = db.tablePage(this.Config.DataQuery, this.Config.AliasFrom, batchStart, bs);
+                        batchStart += bs;
+
+                        //this happens when all-records % pBlockSize == 0
+                        //we do not want to call the callback-fn
+                        if (daten.length == 0)
+                            daten = null;
+                    }
+                    else
+                    {
+                        if (this.idList == undefined)
+                        {
+                            this.writeLog(this.LogLevels.Warning, "using \"IdQuery\" and \"IdColumn\" is an obsolte method for getting sql-data in blocks");
+                            this.idList = this.createIDList();
+                        }
+
+                        //legacy method for batch-processsing
+                        if(batchStart < this.idList.length)  // as long as we got something to do
+                        {
+                            batchList = this.idList.slice(batchStart, batchStart + bs);
+                            batchStart += bs;
+
+                            // get data of the next batch
+                            var sql = this.Config.DataQuery.replace(/\$\$id/i, idcolumn + " in ('" + batchList.join("','") + "')" );
+                            daten = db.table(sql, this.Config.AliasFrom);
+                        }
+                        else
+                        {
+                            this.idList = undefined;
+                            daten = null;
+                        }
+                    }
+                }
+                else
+                {
+                    // return complete result set
+                    if(batchNum > 1)
+                        daten = null;
+                    else
+                        daten = db.table(this.Config.DataQuery, this.Config.AliasFrom);
+                }
+                tt = datetime.date() - tt;
+                exectimer["getdata"] += tt;
+                return daten;
+                break;
+        }
+    }
+    this.createIDList = function()
+    {
+        var tt = datetime.date();   // exectimer
+        var resIDList;
+
+        if(pConfig.IdQuery != undefined && pConfig.DataQuery != undefined && pConfig.IdColumn != undefined)
+        {
+            // set the id column for the batch retrieval
+            idcolumn = this.Config.IdColumn;
+
+            // set multi-batch import
+            importtype = "M";
+
+            // get the list of primary keys
+            resIDList = db.array(db.COLUMN, pConfig.IdQuery, pConfig.AliasFrom);
+            tt = datetime.date() - tt;
+            exectimer["getdata"] += tt;
+        }
+        return resIDList;
+    }
+
+    //	@param String pMessage -- die Meldung, die geloggt werden soll
+    //	@param String pLevel
+    this.writeLog = function(pLevel, pMessage)
+    {
+        var tt = datetime.date();
+
+        // if logging has been "oursourced", just call the callback function and do nothing
+        if(this.LogCallback != null && typeof(this.LogCallback) == "function")
+        {
+            this.LogCallback(pLevel, pMessage);
+        }
+        else if(pLevel <= this.LogLevel) 		// shall we output this message?
+        {
+            var logprefix = "[IMPORTER]@" + datetime.toDate(datetime.date(), translate.text("yyyy-MM-dd HH:mm:ss"), "UTC") + " UTC: ";
+            var logline = logprefix + pMessage;
+            if (this.enableLogBuffer)
+                logBuffer.push(logline);
+
+            if(this.Log == "LOGFILE")
+            {
+                logging.log(logline);
+            }
+            else
+            {
+                if(this.Log == "CONSOLE")
+                {
+                    logging.log(logline);
+                }
+                else if(this.Log != "" && this.Log != undefined)
+                {
+                    // log in globalvar
+                    if(this.Log.split(".")[0].toLowerCase() == "$global")
+                    {
+                        var s = logline + "\r\n";
+                        vars.set(this.Log, vars.getString(this.Log) + s);
+                    }
+                }
+            }
+        }
+
+        tt = datetime.date() - tt;
+        exectimer["logging"] += tt;
+    }
+
+
+    //	retrieve all log messages currently in log buffer
+    this.getLogMessages = function(){
+        return logBuffer;
+    }
+    // return true, if running in client context, false for a server context
+    this.isClientProcess = function(){
+        return vars.getString("$sys.isclient") == "true";
+    }
+
+
+    this.getFileContent = function(pFilename, pDataType)
+    {
+        var resContent;
+        var cp;
+
+        if(this.isClientProcess() == true)   // use doClientIntermediate
+        {
+            try
+            {
+                resContent = swing.doClientIntermediate(swing.CLIENTCMD_GETDATA, [pFilename, pDataType, this.fileInputCharset]);
+            }
+            catch(ex)
+            {
+                logging.show(ex)
+                resContent = "";
+            }
+        }
+        else   // use fileIO
+        {
+            try
+            {
+                resContent = fileIO.getData(pFilename, util.DATA_TEXT, this.fileInputCharset);
+            }
+            catch(ex)
+            {
+                logging.log( ex );
+                resContent = "";
+            }
+        }
+        return resContent;
+    }
+
+
+    this.dumpRecord = function(pTable, pColumns, pTypes, pValues, pCondition)
+    {
+        var PADDING = "................................";
+        var s = "";
+        if(pCondition != undefined) s += "update " + pTable + " where " + pCondition; else s += "insert " + pTable;
+        s += "\n";
+        for(var i=0; i < pColumns.length; i++)
+        {
+            s += "  " + pColumns[i] + PADDING.substr(0, 32- pColumns[i].length) + ": |" + pValues[i] + "|\n";
+        }
+
+        s += "\n";
+
+        return s;
+    }
+
+
+    this.insertData = function(pTable, pColumns, pTypes, pValues, pAlias)
+    {
+        var tt = datetime.date();
+        var resData = true;
+        if(this.Preview == false)
+        {
+            //better safe than sorry ...
+            try
+            {
+                if(pTable.toLowerCase() != "var")
+                {
+                    if(this.DataType[pTable] == undefined) this.writeLog(this.LogLevels.Error, "Table " + pTable + " not exist in database");
+                    else
+                    {
+                        this.writeLog(this.LogLevels.Debug, "INSERT for [" + pAlias + "].[" + pTable + "]");
+                        this.writeLog(this.LogLevels.Preview, this.dumpRecord(pTable, pColumns, pTypes, pValues));
+                        //exist already "USER_NEW" and/or "DATE_NEW" in the columns resultset?
+                        var uNew = false;
+                        var dNew = false;
+                        for(var i = 0; i < pColumns.length; i++)
+                        {
+                            if(uNew == false && pColumns[i].toUpperCase() == "USER_NEW")
+                                uNew = true;
+
+                            if(dNew == false && pColumns[i].toUpperCase() == "DATE_NEW")
+                                dNew = true;
+                        }
+                        // process audit columns automagically
+                        if(uNew == false && this.DataType[this.getTableCase(pTable)][this.getColumnCase("USER_NEW")] != undefined)
+                        {
+                            pColumns.push(this.getColumnCase("USER_NEW"));
+                            if (pTypes != null) pTypes.push(this.DataType[this.getTableCase(pTable)][this.getColumnCase("USER_NEW")]);
+                            pValues.push(this.ImportUser);
+                        }
+                        if(dNew == false && this.DataType[pTable][this.getColumnCase("DATE_NEW")] != undefined)
+                        {
+                            pColumns.push(this.getColumnCase("DATE_NEW"));
+                            if(pTypes != null) pTypes.push(this.DataType[this.getTableCase(pTable)][this.getColumnCase("DATE_NEW")]);
+                            pValues.push(datetime.date());
+                        }
+
+                        this.insertArray.push([this.getTableCase(pTable), pColumns, pTypes, pValues, pAlias]);
+                    }
+                }
+            }
+            catch(ex)
+            {
+                this.writeLog(this.LogLevels.Error, "Exception at insertData for record: " + getRecordString(pColumns, pValues));
+                logging.log(ex);
+                resData = false;
+            }
+        }
+        else
+        {
+            this.writeLog(this.LogLevels.Preview, "Insert into table " + pTable);
+            this.writeLog(this.LogLevels.Preview, this.dumpRecord(pTable, pColumns, pTypes, pValues));
+        }
+
+        tt = datetime.date() - tt;
+        exectimer["insertData"] += tt;
+        return resData;
+    }
+
+    this.updateData = function(pTable, pColumns, pTypes, pValues, pCondition, pAlias)
+    {
+        var tt = datetime.date();
+        var theCols;
+        var theTypes;
+        var theValues;
+
+        var resultData = true;
+        if(this.Preview == false)
+        {
+            try
+            {
+                if(pTable.toLowerCase() != "var")
+                {
+                    if(this.DataType[pTable] == undefined) this.writeLog("Table " + pTable + " not exist in database");
+                    else
+                    {
+                        this.writeLog(this.LogLevels.Debug, "UPDATE for alias [" + pAlias + "].[" + pTable + "]");
+                        this.writeLog(this.LogLevels.Preview, this.dumpRecord(pTable, pColumns, pTypes, pValues, pCondition));
+
+                        if (this.CompleteUpdate == false)   // check for changed database values and use only changed columns for update
+                        {
+                            var uColumns = new Array();
+                            var uValues = new Array();
+                            var uTypes = new Array();
+                            var oldValues = db.array(db.ROW, "select " + pColumns.join(", ") + " from " + pTable + " where " + pCondition, pAlias);
+                            for (var dsi = 0; dsi < oldValues.length; dsi++)
+                            {
+                                if (oldValues[dsi] != pValues[dsi])
+                                {
+                                    //get the values from the validate target
+                                    uColumns.push(pColumns[dsi]);
+                                    uValues.push(pValues[dsi]);
+                                    if (pTypes != null) uTypes.push(pTypes[dsi]);
+                                }
+                            }
+                            theCols = uColumns;
+                            theTypes = uTypes;
+                            theValues = uValues;
+                        }
+                        else   // update all columns, so use default column set
+                        {
+                            theCols = pColumns;
+                            theTypes = pTypes;
+                            theValues = pValues;
+                        }
+
+                        if(theCols.length > 0)
+                        {
+                            //show the old and the new data only if anything changed
+                            this.writeLog(this.LogLevels.Preview, "New Data: " + pValues.join("|"));
+                            if (oldValues != undefined)
+                                this.writeLog(this.LogLevels.Preview, "Old Data: " + oldValues.join("|"));
+                        }
+
+                        var minchanges = 0;
+                        var dEdit = false;
+                        var uEdit = false;
+
+                        for(var i = 0; i < theCols.length; i++)
+                        {
+                            if(uEdit == false && theCols[i].toUpperCase() == "USER_EDIT")
+                                uEdit = true;
+
+                            if(dEdit == false && theCols[i].toUpperCase() == "DATE_EDIT")
+                                dEdit = true;
+                        }
+
+
+                        // process audit columns automagically
+                        if(uEdit == false && this.DataType[this.getTableCase(pTable)][this.getColumnCase("USER_EDIT")] != undefined)
+                        {
+                            theCols.push(this.getColumnCase("USER_EDIT"));
+                            if(pTypes != null) theTypes.push(this.DataType[this.getTableCase(pTable)][this.getColumnCase("USER_EDIT")]);
+                            theValues.push(this.ImportUser);
+                            minchanges++;
+                        }
+                        if(dEdit == false && this.DataType[this.getTableCase(pTable)][this.getColumnCase("DATE_EDIT")] != undefined)
+                        {
+                            theCols.push(this.getColumnCase("DATE_EDIT"));
+                            if(pTypes != null) theTypes.push(this.DataType[this.getTableCase(pTable)][this.getColumnCase("DATE_EDIT")]);
+                            theValues.push(datetime.date());
+                            minchanges++;
+                        }
+                        if(this.CompleteUpdate == false)
+                        {
+                            if(theCols.length > minchanges)
+                            {
+                                this.updateArray.push([this.getTableCase(pTable), theCols, theTypes, theValues, pCondition, pAlias]);
+                            }
+                        }
+                        else
+                        {
+                            this.updateArray.push([this.getTableCase(pTable), theCols, theTypes, theValues, pCondition, pAlias]);
+                        }
+                    }
+                }
+            }
+            catch(ex)
+            {
+                resultData = false;
+                this.writeLog(this.LogLevels.Error, "Exception at updateData for record: " + getRecordString(pColumns, pValues));
+                logging.log(ex);
+            }
+        }
+        else
+        {
+            this.writeLog(this.LogLevels.Preview, "Update table " + pTable);
+            this.writeLog(this.LogLevels.Preview, this.dumpRecord(pTable, pColumns, pTypes, pValues, pCondition));
+        }
+        tt = datetime.date() - tt;
+        exectimer["updateData"] += tt;
+        return resultData;
+    }
+
+    //		set default action for a mapping call, if action has not been specified
+    this.setDefaultAction = function(pObject)
+    {
+        if(pObject.Action == undefined)  // set reasonable defaults for Action, if not specified
+        {
+            if(this.Config.ImportCommand == "insert") pObject.Action = "I";
+            else
+            if(this.Config.ImportCommand == "update") pObject.Action = "U";
+            else
+            if(this.Config.ImportCommand == "insert+update") pObject.Action = "I+U";
+        }
+    }
+
+    //		ATTENTION!! This is the *new* version and not the same as resolveSymbols!!
+    //
+    //		resolve symbol to get import data
+    //		may contain literals string and {#} and {tbl.col} symbols
+    //		if undefined or empty expression is provided, return an empty string
+    this.resolveSymbol = function(pObject, pExpression, pEvalScript)
+    {
+        var expr;
+
+        if((pExpression != undefined) && (pExpression != ""))
+        {
+            var inp = this.InputRecord;
+            var out = this.OutputRecord;
+            var cCase = this.ColumnCase;
+            var tCase = this.TableCase;
+            var uCase = this.Cases.Upper;
+
+            this.setDefaultAction(pObject);
+            var obj = pObject;
+
+            let self = this;
+
+            expr = pExpression.toString();
+            expr = expr.replace(/\{([._a-zA-Z0-9]+)\}/ig,
+                function(pMatch, pNumber)
+                {
+                    if(isNaN(Number(pNumber)))
+                    {
+                        var varname = pNumber.split(".");
+                        var res;
+
+                        // action verwenden, wenn keine da => importcommand auslesen
+                        var action = obj.Action;
+                        if(out[ self.getTableCase(varname[0]) ] != undefined && out[ self.getTableCase(varname[0]) ][ self.getColumnCase(varname[1]) ] != undefined)
+                        {
+                            switch(obj.Action)
+                            {
+                                case "I":
+                                    res = out[ self.getTableCase(varname[0]) ][ self.getColumnCase(varname[1]) ]["I"];
+                                    break;
+                                case "U":
+                                    res = out[ self.getTableCase(varname[0]) ][ self.getColumnCase(varname[1]) ]["U"];
+                                    break;
+                                case "I+U":
+
+                                    res = out[ self.getTableCase(varname[0]) ][ self.getColumnCase(varname[1]) ]["U"];																					//2009-06-16  TR
+                                    if(res == undefined || res == "") res = out[ self.getTableCase(varname[0])][ self.getColumnCase(varname[1]) ]["I"];  //2009-06-16  TR
+
+                                    break;
+                            }
+                        }
+                        else  // varname does not exist as a property of out
+                        {
+                            res = undefined;
+                        }
+                        if(res == undefined) res = "";  // blank out NULL values
+
+                        //transform ' to \\u0027 which is in eval a \u0027; transform " to \\u0022 which is in eval a \u0022
+                        //needed for eval like: "'3'2' != ''"
+                        if(pEvalScript)
+                            return res.replace("'", "\\\\u0027", "g").replace("\"", "\\\\u0022", "g");
+                        else
+                            return res;
+                    }
+                    else
+                    {
+                        //transform ' to \\u0027 which is in eval a \u0027; transform " to \\u0022 which is in eval a \u0022
+                        //needed for eval like: "'3'2' != ''"
+                        if(pEvalScript)
+                            return inp[Number(pNumber)].replace("'", "\\\\u0027", "g").replace("\"", "\\\\u0022", "g");
+                        else
+                            return inp[Number(pNumber)];
+                    }
+                } );
+            if(pEvalScript == true)
+                expr = eval(expr);
+        }
+        else
+        {
+            expr = "";
+        }
+        return expr;
+    }
+
+    //	  read column type information for all columns in all tables of the alias specified
+    this.getDataTypes = function(pAlias)
+    {
+        var tables = db.getTables(pAlias);
+        var dataTypes = new Object();
+        for(var i=0; i < tables.length; i++)
+        {
+            if (tables[i] != "trace_xe_action_map" && tables[i] != "trace_xe_event_map")
+            {
+                var cols = db.getColumns(tables[i], pAlias);
+                var types = db.getColumnTypes(tables[i], cols, pAlias);
+                dataTypes[ tables[i] ] = new Object();  // create sub-object to hold columns
+                for(var j=0; j < cols.length; j++) dataTypes[tables[i]][cols[j]] = types[j];
+            }
+        }
+        return dataTypes;   // return object with type information
+    }
+
+    //		sets the output record buffer according to "Action" performed
+    this.setOutput = function(pObject, pValue)
+    {
+        try
+        {
+            var target = pObject.Target.split(".");
+            this.setDefaultAction(pObject);
+            // make sure we do have an output buffer
+            if(this.OutputRecord[ this.getTableCase(target[0]) ] == undefined) this.OutputRecord[ this.getTableCase(target[0]) ] = new Object();
+            if(this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ] == undefined) this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ] = new Object();
+            switch(pObject.Action)
+            {
+                case "I":
+                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["I"] = pValue;
+                    break;
+                case "U":
+                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["U"] = pValue;
+                    break;
+                case "I+U":
+                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["I"] = pValue;    //2009-06-16  TR
+                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["U"] = pValue;    //2009-06-16  TR
+                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["I+U"] = pValue;  //2009-06-16  TR
+                    break;
+            }
+        }
+        catch(ex)
+        {
+            this.writeLog(this.LogLevels.Error, "Property <Target> not set for mapping object!");
+            logging.log(ex);
+            logging.log("[IMPORTER] Property <Target> not set for mapping object!");
+        }
+    }
+    //		get the content of the output record buffer according to "Action" performed
+    this.getOutput = function(pObject, pTarget)
+    {
+        var target;
+        var action;
+
+        target = pTarget.split(".");
+        if(pObject != undefined) action = pObject.Action; else action = undefined;
+        if(action == undefined)  // set reasonable defaults for Action, if not specified
+        {
+            if(this.Config.ImportCommand == "insert") action = "I";
+            else
+            if(this.Config.ImportCommand == "update") action = "U";
+            else
+            if(this.Config.ImportCommand == "insert+update") action = "I+U";
+        }
+        var resAction;
+        switch(action)
+        {
+            case "I":
+                resAction = this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1])]["I"];
+                break;
+            case "U":
+                resAction = this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["U"];
+                break;
+            case "I+U":
+                resAction = this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["U"];
+                if(resAction == undefined || resAction == "") resAction = this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["I"];
+                break;
+        }
+        if(resAction == undefined) resAction = "";   // blank out undefined
+        return resAction;
+    }
+
+
+    //		generates condition clause (without "WHERE") to check for existing key column values
+    this.generateKeyCondition = function(pTable)
+    {
+        var whereclause = "";
+        for(var col in this.KeyColumn[pTable])
+        {
+            var tmp = this.KeyColumn[pTable][col];  // contains I | I;U | I+U
+            if(tmp != "")
+            {
+                var value = this.OutputRecord[pTable][col]["U"];
+                if(value == undefined || value == "")
+                    value = this.OutputRecord[pTable][col]["I+U"];
+                if(value == undefined || value == "")
+                    value = this.OutputRecord[pTable][col]["I"];
+
+                // we cannot distinguish between an empty string and NULL in Jdito,
+                // so if get an empty string for the value, we do a check for empty string OR NULL
+                if(value == "")
+                {
+                    whereclause += " and (" + col + " = '' OR " + col + " is null)";
+                }
+                else
+                {
+                    whereclause += " and " + col + " = '" + db.quote(value, this.Config.AliasTo) + "'";
+                }
+            }
+        }
+        // remove leading "and" from where clause
+        if(whereclause.substr(0, 4) == " and") whereclause = whereclause.substr(4, whereclause.length);
+
+        return whereclause;
+    }
+    /*
+    * yield a string representation of the record in pColumns and pValues for logging
+    * purposes and debugging.
+    *
+    * @param {String[]} pColumns req columns that are represented
+    * @param {String[]} pValues req values that are represented
+    *
+    * @return {String}
+    */
+    function getRecordString(pColumns, pValues)
+    {
+        var s = "";
+        for(var i=0; i < pColumns.length; i++)
+        {
+            var v = "";
+            if(pValues[i]) v = pValues[i].toString();
+            if(v.length > 40) v = v.substr(0,39) + "...";
+            s += "  COL: " + pColumns[i] + ": " + v + "\n";
+        }
+
+        return s;
+    }
+
+    //Converts a string with the tablename in upper or lower case
+    this.getTableCase = function(pName)
+    {
+        if(this.TableCase == this.Cases.Upper)
+            return pName.toUpperCase();
+        else if (this.TableCase == this.Cases.Lower)
+            return pName.toLowerCase();
+        else
+            return pName;
+    }
+
+    //Converts a string with the columnname in upper or lower case
+    this.getColumnCase = function(pName)
+    {
+        if (this.ColumnCase == this.Cases.Upper)
+            return pName.toUpperCase();
+        else if (this.ColumnCase == this.Cases.Lower)
+            return pName.toLowerCase();
+        else
+            return pName;
+    }
+
+    this.doIfCheck = function(pObject)
+    {
+        if (pObject.DoIf == undefined) return true;
+
+        if (typeof(pObject.DoIf) == "function")
+        {
+            var expr;
+            if(pObject.Source != undefined) expr = this.InputRecord[pObject.Source];
+            if(pObject.Value != undefined) expr = this.resolveSymbol(pObject, pObject.Value, pObject.Eval);
+            if(pObject.Map != undefined && pObject.Index) expr = pObject.Map[this.resolveSymbol(pObject, pObect.Index, pObject.Eval)];
+            return pObject.DoIf.call(this, this.InputRecord, expr, pObject);
+        }
+        
+        return (this.resolveSymbol(pObject, pObject.DoIf, true));
+    }
+}
+
+/**
+ * Data handler for csv paging
+ *
+ * @param pCurrentBatchNum
+ * @param pBatchSize
+ */
+function batchCsvLoad(pCurrentBatchNum, pBatchSize)
+{
+    if (this.BatchStart == undefined) this.BatchStart = 0;
+    if (this.Config.RowSeparator != undefined) this.writeLog(this.LogLevels.Warning, "'RowSeparator' option is currently not supported!")
+    var dataStr = "";
+    var fn = this.Config.DataFile;
+    var bs = pBatchSize;
+    var process = this.Config.Processname;
+    if (!fileIO.exists(fn))
+        throw new Error(translate.withArguments("file '%0' does not exist or you have got no permission on this file", [fn]));
+    else if (!fileIO.canRead(fn))
+        throw new Error(translate.withArguments("file '%0' cannot be read", [fn]));
+    const MAX_LOOP_SIZE = this.Config.BatchSize;
+    do
+    {
+        dataStr = null;
+        var loopSize = MAX_LOOP_SIZE;
+        try
+        {
+            dataStr = fileIO.getBulkData(fn, util.DATA_TEXT, this.fileInputCharset, this.BatchStart, bs);
+        }
+        catch(ex)
+        {
+            break;
+        }
+        this.BatchStart += bs;
+        whileMultiline: while (dataStr.slice(-2) != "\r\n" && (dataStr.slice(-1) == "\n" || dataStr.slice(-1) == "\r"))//we have to load more Data
+        {
+            try
+            {
+                dataStr += fileIO.getBulkData(fn, util.DATA_TEXT, this.fileInputCharset, this.BatchStart, 1);
+            }
+            catch(ex)
+            {
+                this.writeLog(this.LogLevels.Warning, ex);
+                break;
+            }
+            this.BatchStart++;
+            if (loopSize == 0)
+            {
+                this.writeLog(this.LogLevels.Error, "MAX_LOOP_SIZE reached; Abort");
+                break whileMultiline;
+            }
+            loopSize--;
+        }
+        if (dataStr.slice(-2) == "\r\n")
+            dataStr = dataStr.slice(0, -2);
+        var rs = this.Config.RowSeparator;
+        if(rs == undefined) rs = "\r\n";
+        var cs = this.Config.ColumnSeparator;
+        if(cs == undefined) cs = ";";
+        var sd = this.Config.StringDelimiter;
+        if(sd == undefined) sd = "";
+        return text.parseCSV(dataStr, rs, cs, sd);
+    }
+    while(dataStr != null);
+    return null;
+}
+
+
diff --git a/process/_test_importer/_test_importer.aod b/process/_test_importer/_test_importer.aod
new file mode 100644
index 0000000000000000000000000000000000000000..054240e430569eceaeff6e6c6e1a7731a30a849d
--- /dev/null
+++ b/process/_test_importer/_test_importer.aod
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
+  <name>_test_importer</name>
+  <title>Importer</title>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <process>%aditoprj%/process/_test_importer/process.js</process>
+  <variants>
+    <element>EXECUTABLE</element>
+  </variants>
+</process>
diff --git a/process/_test_importer/process.js b/process/_test_importer/process.js
new file mode 100644
index 0000000000000000000000000000000000000000..ad51271dd609329d2675e3ecc01b8f153efabf40
--- /dev/null
+++ b/process/_test_importer/process.js
@@ -0,0 +1,444 @@
+import("system.text");
+import("system.logging");
+import("Util_lib");
+import("ImporterTest_lib");
+import("Importer_lib");
+import("ImporterMappingFunctions_lib");
+
+var testfunctions = {
+    iKeyword: {
+        "insertKeywordNew": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iKeyword, {Container: 0, Keyword: 1}]]
+            }
+            return runFn(config);
+        },
+        "insertKeywordContainer": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iKeyword, {Container: 0, Keyword: 1}]]
+            }
+            return runFn(config);
+        },
+        "insertKeywordSame": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iKeyword, {Container: 0, Keyword: 1}]]
+            }
+            return runFn(config);
+        },
+        "insertKeywordMissing": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iKeyword, {Container: 0, Keyword: 1}]]
+            }
+            return runFn(config);
+        }
+    },
+    iAttribute: {
+        "insertAttributeNew": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iAttribute, {Attribute: 0, AType: 1}]]
+            }
+            return runFn(config);
+        },
+        "insertAttributeUsage": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iAttribute, {Attribute: 0, AType: 1, OType: 2}]]
+            }
+            return runFn(config);
+        },
+        "insertAttributeRelation": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iAttribute, {Attribute: 0, AType: 1, OType: 2, OID: 3, Value: 4}]]
+            }
+            return runFn(config);
+        },
+        "insertAttributeMissing": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iAttribute, {Attribute: 0, AType: 1}]]
+            }
+            return runFn(config);
+        },
+        "insertAttributeSame": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iAttribute, {Attribute: 0, AType: 1, OType: 2, OID: 3, Value: 4}]]
+            }
+            return runFn(config);
+        }
+    },
+    iKeywordAttribute: {
+        "insertKeywordAttributeNew": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iKeywordAttribute, {Attribute: 0, AType: 1, Container: 2}]]
+            }
+            return runFn(config);
+        },
+        "insertKeywordAttributeRelation": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iKeywordAttribute, {Attribute: 0, AType: 1, Container: 2, Keyword: 3, Value: 4}]]
+            }
+            return runFn(config);
+        },
+        "insertKeywordAttributeMissing": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iKeywordAttribute, {Attribute: 0, AType: 1, Container: 2, Keyword: 3, Value: 4}]]
+            }
+            return runFn(config);
+        }
+    },
+    iComm: {
+        "insertCommNew": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iComm, {Address: 0, Medium: 1, ContactID: 2}]]
+            }
+            return runFn(config);
+        },
+        "insertCommMissing": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iComm, {Address: 0, Medium: 1, ContactID: 2}]]
+            }
+            return runFn(config);
+        }
+    },
+    iActivityLink: {
+        "insertActivityLinkNew": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iActivityLink, {ActivityID: 0, OID: 1, OType: 2}]]
+            }
+            return runFn(config);
+        },      
+        "insertActivityLinkMissing": function(runFn) {
+            var config = {
+                AliasTo: "Data_alias",
+                DataFunction: function(pBatchNum, pBatchSize) {
+                    if (pBatchNum > 1) return null;
+                    return this.fixtures;
+                }.bind(this),
+                ImportCommand: "insert+update",
+                Mapping: [[iActivityLink, {ActivityID: 0, OID: 1, OType: 2}]]
+            }
+            return runFn(config);
+        }
+    }
+}  
+
+var fixtures = {
+    iKeyword: {
+        "insertKeywordNew": function() {
+            return [
+                ["ContainerTest1", "KeywordTest1"]               
+            ];
+        },
+        "insertKeywordContainer": function() {
+            return [
+                ["ContainerTest1", "KeywordTest2"],
+                ["ContainerTest1", "KeywordTest3"],
+                ["ContainerTest1", "KeywordTest4"],
+                ["ContainerTest1", "KeywordTest5"]
+            ];
+        },
+        "insertKeywordSame": function() {
+            return [
+                ["ContainerTest1", "KeywordTest1"],
+                ["ContainerTest1", "KeywordTest2"],
+                ["ContainerTest1", "KeywordTest3"],
+            ];
+        },
+        "insertKeywordMissing": function() {
+            return [
+                ["", "KeywordTest2"],
+                [1234, ""],
+            ];
+        }
+    },
+    iAttribute: {
+        "insertAttributeNew": function() {
+            return [
+                ["TestString", $AttributeTypes.TEXT.toString()],
+                ["TestCombo1.TestCombo2.TestCombo3", $AttributeTypes.COMBOVALUE.toString()],
+                ["TestBool", $AttributeTypes.BOOLEAN.toString()],
+                ["TestNum", $AttributeTypes.NUMBER.toString()]
+            ];
+        },
+        "insertAttributeUsage": function() {
+            return [
+                ["TestUsage1", $AttributeTypes.NUMBER.toString(), "Organisation"],
+                ["TestUsage1", $AttributeTypes.NUMBER.toString(), "Person"]
+            ];
+        },
+        "insertAttributeRelation": function() {
+            return [
+                ["TestRel1", $AttributeTypes.TEXT.toString(), "Person", "666", "six hundred sixty six"],
+                ["TestRel1", $AttributeTypes.TEXT.toString(), "Person", "777", "seven hundred seventy seven"],
+                ["TestRel2", $AttributeTypes.NUMBER.toString(), "Organisation", "888", 888],
+                ["TestRel2", $AttributeTypes.NUMBER.toString(), "Organisation", "999", 999]
+            ];
+        },
+        "insertAttributeMissing": function() {
+            return [
+                ["", $AttributeTypes.TEXT.toString()],
+                ["TestNum", ""],
+                ["", 234]
+            ];
+        },
+        "insertAttributeSame": function() {
+            return [
+                ["TestRel1", $AttributeTypes.TEXT.toString(), "Person", "666", "six hundred sixty four"],
+                ["TestRel1", $AttributeTypes.TEXT.toString(), "Person", "777", "seven hundred seventy five"]               
+            ];
+        }
+    },
+    iKeywordAttribute: {
+        "insertKeywordAttributeNew": function() {
+            return [
+                ["KeyAttribute1", $AttributeTypes.TEXT.toString(), "ContainerTest2"],
+                ["KeyAttribute2", $AttributeTypes.NUMBER.toString(), "ContainerTest2"],
+                ["KeyAttribute3", $AttributeTypes.TEXT.toString(), "ContainerTest3"]
+            ];
+        },
+        "insertKeywordAttributeRelation": function() {
+            return [
+                ["KeyAttRel1", $AttributeTypes.TEXT.toString(), "ContainerTest2", "KeywordTest6", "test value"],
+                ["KeyAttRel2", $AttributeTypes.TEXT.toString(), "ContainerTest2", "KeywordTest7", "test value"],
+                ["KeyAttribute2", $AttributeTypes.NUMBER.toString(), "ContainerTest1", "KeywordTest8", 888]
+            ]
+        },
+        "insertKeywordAttributeMissing": function() {
+            return [
+                ["KeyAttRel", 7, 1],
+                ["KeyAttRel", "My Format", "wrong format"],
+                []
+            ]
+        }
+    },
+    iComm: {
+        "insertCommNew": function() {
+            return [
+                ["TestAddress1", "1", "TestContact1"],
+                ["TestAddress2", "2", "TestContact1"]
+            ];
+        },
+        "insertCommMissing": function() {
+            return [
+                ["TestAddress", ""],
+                ["", 2, ""]
+            ];
+        }
+    },
+    iActivityLink: {
+        "insertActivityLinkNew": function() {
+            return [
+                ["TestActivityId",  "rowTest1", "Person"],
+                ["TestActivityId", "rowTest2", "Organisation"],
+                ["TestActivityId", "rowTest2", "Person"]
+            ];
+        },
+        "insertActivityLinkMissing": function() {
+            return [
+                ["", "rowTest1", "Person"],
+                ["ActivityId", 6293, "Organisation"]
+            ];
+        }
+    }
+}
+
+var testCases = [
+    {
+        name: "Keyword Test",
+        childs: [
+            {
+                name: "Import new keywords",
+                fn: testfunctions.iKeyword.insertKeywordNew,
+                fixtures: fixtures.iKeyword.insertKeywordNew()
+            },
+            {
+                name: "Import keywords with already existing container",
+                fn: testfunctions.iKeyword.insertKeywordContainer,
+                fixtures: fixtures.iKeyword.insertKeywordContainer()
+            },
+            {
+                name: "Import already existing keywords",
+                fn: testfunctions.iKeyword.insertKeywordSame,
+                fixtures: fixtures.iKeyword.insertKeywordSame()
+            },
+            {   
+                name: "Import keywords with missing/wrong values",
+                fn: testfunctions.iKeyword.insertKeywordMissing,
+                fixtures: fixtures.iKeyword.insertKeywordMissing()
+            }
+            
+        ]       
+    },
+    {
+        name: "Attribute Test",
+        childs: [
+            {
+                name: "Import new attributes",
+                fn: testfunctions.iAttribute.insertAttributeNew,
+                fixtures: fixtures.iAttribute.insertAttributeNew()
+            },
+            {
+                name: "Import new attributes with usage",
+                fn: testfunctions.iAttribute.insertAttributeUsage,
+                fixtures: fixtures.iAttribute.insertAttributeUsage()
+            },
+            {
+                name: "Import new attributes with usage and relation",
+                fn: testfunctions.iAttribute.insertAttributeRelation,
+                fixtures: fixtures.iAttribute.insertAttributeRelation()
+            },
+            {
+                name: "Import attributes with missing/wrong values",
+                fn: testfunctions.iAttribute.insertAttributeMissing,
+                fixtures: fixtures.iAttribute.insertAttributeMissing()
+            },
+            {
+                name: "Import already existing attributes",
+                fn: testfunctions.iAttribute.insertAttributeSame,
+                fixtures: fixtures.iAttribute.insertAttributeSame()
+            }
+        ]
+    },
+    {
+        name: "Keyword Attribute Test",
+        childs: [
+            {
+                name: "Import new keyword attributes",
+                fn: testfunctions.iKeywordAttribute.insertKeywordAttributeNew,
+                fixtures: fixtures.iKeywordAttribute.insertKeywordAttributeNew()
+            },
+            {
+                name: "Import keyword attribute relations",
+                fn: testfunctions.iKeywordAttribute.insertKeywordAttributeRelation,
+                fixtures: fixtures.iKeywordAttribute.insertKeywordAttributeRelation()
+            },
+            {
+                name: "Import keyword attributes with missing/wrong values",
+                fn: testfunctions.iKeywordAttribute.insertKeywordAttributeMissing,
+                fixtures: fixtures.iKeywordAttribute.insertKeywordAttributeMissing()
+            }
+        ]
+    },
+    {
+        name: "Communication Test",
+        childs: [
+            {
+                name: "Import new communication entries",
+                fn: testfunctions.iComm.insertCommNew,
+                fixtures: fixtures.iComm.insertCommNew()
+            },
+            {
+                name: "Import communication entries with missing/wrong values",
+                fn: testfunctions.iComm.insertCommMissing,
+                fixtures: fixtures.iComm.insertCommMissing()
+            }
+        ]
+    },
+    {
+        name: "Activitylink Test",
+        childs: [
+            {
+                name: "Import new activity link entries",
+                fn: testfunctions.iActivityLink.insertActivityLinkNew,
+                fixtures: fixtures.iActivityLink.insertActivityLinkNew()
+            },
+            {
+                name: "Import activity link entries with missing/wrong values",
+                fn: testfunctions.iActivityLink.insertActivityLinkMissing,
+                fixtures: fixtures.iActivityLink.insertActivityLinkMissing()
+            }
+        ]
+    }
+]
+
+
+var impTest = new ImporterTest();
+impTest.startImporter(testCases);
\ No newline at end of file