diff --git a/process/Attribute_lib/process.js b/process/Attribute_lib/process.js
index a7c705b9d04d5a254eaa7baa4196eb5b0cf388fc..488b7d7d594b4e979d0351441071cd4f304ee85e 100644
--- a/process/Attribute_lib/process.js
+++ b/process/Attribute_lib/process.js
@@ -1,829 +1,873 @@
-import("system.util");
-import("system.datetime");
-import("system.translate");
-import("system.neon");
-import("system.vars");
-import("system.db");
-import("Sql_lib");
-import("Keyword_lib");
-
-/**
- * Provides functions for the work with attributes, like
- * listing the available attributes for a context.
- * Don't instanciate this!
- * 
- * @class
- */
-function AttributeUtil () {}
-
-/**
- * Gives an array of all available attributes for a context. This is used in the possibleItems
- * process for the attribute id in AttributeRelation
- * 
- * @param {String} pObjectType the object type (= context)
- * @param {boolean} [pIncludeGroups=false]
- * @param {String[]} [pFilteredAttributeIds=[]] Whitleist of attribute ids
- * @param {Object} [pAttributeCount=null] Object with attribute ids and their count
- * 
- * @return {String[]} array of attributeIds
- */
-AttributeUtil.getPossibleAttributes = function (pObjectType, pIncludeGroups, pFilteredAttributeIds, pAttributeCount)
-{
-    if (pObjectType == null || (pFilteredAttributeIds && pFilteredAttributeIds.length == 0))
-        return [];
-    
-    var attrSql = "select AB_ATTRIBUTEID from AB_ATTRIBUTE"
-        + " join AB_ATTRIBUTEUSAGE  on AB_ATTRIBUTEID = AB_ATTRIBUTE_ID";
-    var attrCond = SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
-        .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# <> ?")
-        .and("ATTRIBUTE_ACTIVE = 1");
-    
-    if (pAttributeCount)
-    {
-        for (let attributeId in pAttributeCount)
-        {
-            attrCond.andSqlCondition(
-                SqlCondition.begin()
-                    .orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", attributeId, "# != ?")
-                    .orPrepare("AB_ATTRIBUTEUSAGE.MAX_COUNT", pAttributeCount[attributeId], "# > ?")
-                    .or("AB_ATTRIBUTEUSAGE.MAX_COUNT is null")
-            );
-        }
-    }
-        
-    if (pFilteredAttributeIds)
-    {
-        var filteredIdsCondition = new SqlCondition();
-        var filteredIdChildren = AttributeUtil.getAllChildren(pFilteredAttributeIds);
-        pFilteredAttributeIds.concat(filteredIdChildren).forEach(function(id) 
-        {
-            this.orPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", id);
-        }, filteredIdsCondition);
-
-        attrCond.andSqlCondition(filteredIdsCondition);
-    }
-
-    if (!pIncludeGroups)
-        attrCond.and("ATTRIBUTE_TYPE != '" + $AttributeTypes.GROUP + "'");
-    
-    var attributes = db.array(db.COLUMN, attrCond.buildSql(attrSql));
-    
-    return attributes;
-}
-
-/**
- * returns the name of an attribute with all parent attribute names
- * 
- * @param {String} pAttributeId the id of the attribute
- * @param {Boolean} [pSimpleName=false] Use only the name of the attribute and not the names of the parents.
- * 
- * @return {String} the name of the attribute
- */
-AttributeUtil.getFullAttributeName = function (pAttributeId, pSimpleName) 
-{
-    if (pSimpleName == undefined)
-        pSimpleName = false;
-    
-    if (!pAttributeId)
-        return "";
-    if (pSimpleName)
-        return AttributeUtil.getSimpleAttributeName(pAttributeId);
-    var attributeNames = [];
-    var attribute;
-    do {
-        attribute = db.array(db.ROW, SqlCondition.begin()
-            .andPrepare(["AB_ATTRIBUTE", "AB_ATTRIBUTEID", "ATTRIBUTE"], pAttributeId)
-            .buildSql("select ATTRIBUTE.ATTRIBUTE_NAME, PARENT1.ATTRIBUTE_NAME, PARENT2.ATTRIBUTE_NAME, PARENT2.ATTRIBUTE_PARENT_ID \n\
-                from AB_ATTRIBUTE ATTRIBUTE \n\
-                left join AB_ATTRIBUTE PARENT1 on ATTRIBUTE.ATTRIBUTE_PARENT_ID = PARENT1.AB_ATTRIBUTEID \n\
-                left join AB_ATTRIBUTE PARENT2 on PARENT1.ATTRIBUTE_PARENT_ID = PARENT2.AB_ATTRIBUTEID")
-        );
-        if (attribute.length > 0)
-        {
-            attributeNames.push(attribute[0]);
-            if (attribute[1])
-                attributeNames.push(attribute[1]);
-            if (attribute[2])
-                attributeNames.push(attribute[2]);
-            pAttributeId = attribute[3];
-        }
-        else
-            pAttributeId = "";
-    } while (pAttributeId);
-    
-    return attributeNames.reverse().join(" / ");
-}
-
-/**
- * returns the name of an attribute
- * 
- * @param {String} pAttributeId the id of the attribute
- * 
- * @return {String} the name of the attribute
- */
-AttributeUtil.getSimpleAttributeName = function (pAttributeId) 
-{
-    var attributeName = db.cell(SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
-        .buildSql("select ATTRIBUTE_NAME from AB_ATTRIBUTE")
-    );
-
-    return attributeName;
-}
-
-/**
- * returns the ids of all subordinated attributes of an attribute
- * 
- * @param {String|Array} pAttributeIds the id(s) of the attribute(s)
- * 
- * @result {String[]} array with the ids of every subordinated attribute
- */
-AttributeUtil.getAllChildren = function (pAttributeIds)
-{
-    var childIds = [];
-    if (typeof(pAttributeIds) == "string")
-        pAttributeIds = [pAttributeIds];
-        
-    while (pAttributeIds.length > 0)
-    {
-        pAttributeIds = db.array(db.COLUMN, SqlCondition.begin()
-            .and("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID in ('" + pAttributeIds.join("','") + "')")
-            .buildSql("select AB_ATTRIBUTEID from AB_ATTRIBUTE")
-        );
-        if (pAttributeIds.length > 0)
-            childIds = childIds.concat(pAttributeIds);
-    }
-    return childIds;
-}
-
-/**
- * checks if an attribute has attribute relations
- * 
- * @param {String} pAttributeId the id of the attribute
- * 
- * @result {boolean} true if it has relations
- */
-AttributeUtil.hasRelations = function (pAttributeId)
-{
-    if (!pAttributeId)
-        return false;
-    return db.cell(SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
-        .buildSql(
-            "select exists ("
-            +    "select AB_ATTRIBUTERELATIONID from AB_ATTRIBUTERELATION "
-            +    "where AB_ATTRIBUTE_ID = AB_ATTRIBUTEID"
-            + ") from AB_ATTRIBUTE", "1=2"
-        )
-    ) == "true";
-}
-
-/**
- * returns the type of an attribute
- * 
- * @param {String} pAttributeId the id of the attribute
- * 
- * @result {String} attribute type
- */
-AttributeUtil.getAttributeType = function (pAttributeId)
-{
-    var attrTypeSelect = "select ATTRIBUTE_TYPE from AB_ATTRIBUTE";
-    attrTypeSelect = SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
-        .buildSql(attrTypeSelect);
-    return db.cell(attrTypeSelect).trim();
-}
-
-/*********************************************************************************************************************/
-
-/**
- * Provides functions for the work with attributeRelations, getting the value of an attributeRelation for an object.
- * Don't instanciate this!
- * 
- * @class
- */
-function AttributeRelationUtils () {}
-
-/**
- * gets the value of an attributeRelation for one dataset (e. g. a person)
- * 
- * @param {String} pAttributeId attribute-id
- * @param {String} pObjectRowId row-id of the dataset
- * @param {String} [pObjectType=null] object-type
- * @param {String} [pGetViewValue=false] if true the values are resolved and formatted
- * 
- * @return {String|null} the value of the attribute
- */
-AttributeRelationUtils.getAttribute = function (pAttributeId, pObjectRowId, pObjectType, pGetViewValue)
-{
-    var attrCond = SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTERELATION.OBJECT_ROWID", pObjectRowId)
-        .andPrepare("AB_ATTRIBUTERELATION.AB_ATTRIBUTE_ID", pAttributeId);
-    if (pObjectType != null)
-        attrCond.andPrepare("AB_ATTRIBUTERELATION.OBJECT_TYPE", pObjectType);
-    
-    var defaultFields = [
-        "AB_ATTRIBUTE.ATTRIBUTE_TYPE", 
-        "AB_ATTRIBUTE.KEYWORD_CONTAINER", 
-        "COMBOVAL.ATTRIBUTE_NAME"
-    ];
-    var valueFields = AttributeTypeUtil.getAllDatabaseFields();
-    var attributeSql = attrCond.buildSql("select " + defaultFields.join(", ") + ", " + valueFields.join(", ")
-        + " from AB_ATTRIBUTERELATION join AB_ATTRIBUTE on AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID"
-        + " left join AB_ATTRIBUTE COMBOVAL on " + $AttributeTypes.COMBO.databaseField + " = COMBOVAL.AB_ATTRIBUTEID");
-    
-    var attributeValues = db.array(db.ROW, attributeSql);
-    if (!attributeValues.length)
-        return null;
-    
-    let value = attributeValues[AttributeTypeUtil.getTypeColumnIndex(attributeValues[0]) + defaultFields.length];
-    if (pGetViewValue && attributeValues[1].trim() == $AttributeTypes.COMBO)
-        value = attributeValues[2];
-    else if (pGetViewValue)
-        value = AttributeTypeUtil.getAttributeViewValue(attributeValues[0].trim(), value, attributeValues[1]);
-    
-    return value;
-}
-
-/**
- * gets all attributes for a dataset
- * 
- * @param {String} pObjectRowId object rowid
- * @param {String} [pObjectType=null] object-type
- * @param {String} [pUseAttributeIds=false] if true the ids are used instead of the full attribute names
- * @param {String} [pUseIdValues=false] if true the values are not resolved or formatted
- * 
- * @return {String[][]} two-dimensional array a row is [attributeId|attributeName, value]
- */
-AttributeRelationUtils.getAllAttributes = function (pObjectRowId, pObjectType, pUseAttributeIds, pUseIdValues)
-{
-    var attrCond = SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTERELATION.OBJECT_ROWID", pObjectRowId);
-    if (pObjectType != null)
-        attrCond.andPrepare("AB_ATTRIBUTERELATION.OBJECT_TYPE", pObjectType);
-    
-    var defaultFields = [
-        "AB_ATTRIBUTE_ID", 
-        "AB_ATTRIBUTE.ATTRIBUTE_TYPE", 
-        "AB_ATTRIBUTE.KEYWORD_CONTAINER", 
-        "COMBOVAL.ATTRIBUTE_NAME"
-    ];
-    var valueFields = AttributeTypeUtil.getAllDatabaseFields();
-    var attributeSql = attrCond.buildSql("select " + defaultFields.join(", ") + ", " + valueFields.join(", ")
-        + " from AB_ATTRIBUTERELATION join AB_ATTRIBUTE on AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID"
-        + " left join AB_ATTRIBUTE COMBOVAL on " + $AttributeTypes.COMBO.databaseField + " = COMBOVAL.AB_ATTRIBUTEID");
-    
-    var attributeNameMap = {};
-    var attributeValues = db.table(attributeSql).map(function (row) 
-    {
-        let attribute = row[0];
-        if (!pUseAttributeIds)
-        {
-            if (!(attribute in attributeNameMap))
-                attributeNameMap[attribute] = AttributeUtil.getFullAttributeName(attribute);
-            attribute = attributeNameMap[attribute];
-        }
-        let value = row[AttributeTypeUtil.getTypeColumnIndex(row[1]) + defaultFields.length];
-        if (!pUseIdValues && row[1].trim() == $AttributeTypes.COMBO)
-            value = row[3];
-        else if (!pUseIdValues)
-            value = AttributeTypeUtil.getAttributeViewValue(row[1].trim(), value, row[2]);
-        
-        return [attribute, value];
-    });
-    
-    return attributeValues;
-}
-
-/**
- * gets the correct attribute value from a map with values depending on the attribute id
- * 
- * @param {String} pAttributeId the attribute id
- * @param {Object} pValueMap a map with the attribute values and the db fields as keys
- * @param {Boolean} [pGetViewValue=false] if true, get the view value
- * 
- * @return {String|null} the value of the attribute or null if the attribute doesn't exist
- */
-AttributeRelationUtils.selectAttributeValue = function (pAttributeId, pValueMap, pGetViewValue)
-{
-    var sqlSelect = "select ATTRIBUTE_TYPE, KEYWORD_CONTAINER from AB_ATTRIBUTE";
-    var type = db.array(db.ROW, SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
-        .buildSql(sqlSelect)
-    );
-    if (!type.length)
-        return null;
-    
-    type[0] = type[0].trim();
-    var field = AttributeTypeUtil.getDatabaseField(type[0]);
-    var value = pValueMap[field];
-    if (pGetViewValue && type[0] == $AttributeTypes.COMBO)
-    {
-        value = db.cell(SqlCondition.begin()
-            .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", value)
-            .buildSql("select ATTRIBUTE_NAME from AB_ATTRIBUTE")
-        );
-    }
-    else if (pGetViewValue)
-        value = AttributeTypeUtil.getAttributeViewValue(type[0], value, type[1]);
-    
-    return value;
-}
-
-AttributeRelationUtils.getAttributes = function ()
-{
-    //TODO: implement maybe
-}
-
-/**
- * sets the value of an attribute for one dataset (e. g. a person)
- */
-AttributeRelationUtils.setAttribute = function ()
-{
-    //TODO: implement
-}
-
-/**
- * adds rows for attributes with min_count > 0
- * 
- * @param {String} pObjectType the object type
- * @param {String} pConsumer the name of the attribute relation consumer
- */
-AttributeRelationUtils.presetMandatoryAttributes = function (pObjectType, pConsumer)
-{
-    var mandatoryAttributes = db.table(
-        SqlCondition.begin()
-            .andPrepare("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
-            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# <> ?")
-            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.GROUP, "# <> ?")
-            .and("ATTRIBUTE_ACTIVE = 1")
-            .and("MIN_COUNT > 0")
-            .buildSql("select AB_ATTRIBUTE_ID, MIN_COUNT from AB_ATTRIBUTEUSAGE "
-                + "join AB_ATTRIBUTE on AB_ATTRIBUTE_ID = AB_ATTRIBUTEID")
-    );
-    mandatoryAttributes.forEach(function (usage)
-    {
-        //adding an attribute more than 20 times would be too much (having a min_count > 20 is very unlikely)
-        for (let i = 0; i < usage[1] && i < 20; i++)
-            neon.addRecord(pConsumer, {
-                "AB_ATTRIBUTE_ID" : usage[0]
-            });
-    });
-}
-
-/**
- * Checks if the count of the used attributes is valid and returns a message if it's not.
- * 
- * @param {String} pRowId the row id of the entity
- * @param {String} [pObjectType=null] the object type
- * @param {String} pConsumerField the name of the attribute relation consumer
- * 
- * @return {String} the validation message or an empty string if everything is ok
- */
-AttributeRelationUtils.validateAttributeCount = function (pRowId, pObjectType, pConsumerField)
-{
-    var attributeChanges = {};
-    var deletedRows = vars.get("$field." + pConsumerField + ".deletedRows");
-    var changedRows = vars.get("$field." + pConsumerField + ".changedRows");
-    var insertedRows = vars.get("$field." + pConsumerField + ".insertedRows");
-    if (deletedRows)
-    {
-        deletedRows.forEach(function (row)
-        {
-            this[row.UID] = "";
-        }, attributeChanges);
-    }
-    if (changedRows)
-    {
-        changedRows.forEach(function (row)
-        {
-            this[row.UID] = row.AB_ATTRIBUTE_ID;
-        }, attributeChanges);
-    }
-    //get the current count of usages considering the changes
-    var countObj = AttributeRelationUtils.countAttributeRelations(pRowId, pObjectType, attributeChanges);
-    
-    if (insertedRows) //append the new rows
-    {
-        insertedRows.forEach(function (row)
-        {
-            this[row.AB_ATTRIBUTE_ID] = (this[row.AB_ATTRIBUTE_ID] || 0) + 1;
-        }, countObj);
-    }
-    var attributeCondition = SqlCondition.begin();
-    AttributeUtil.getPossibleAttributes(pObjectType).forEach(function (attributeId)
-    {
-        this.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", attributeId);
-    }, attributeCondition);
-    
-    var usageCondition = SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
-        .andSqlCondition(attributeCondition, "1=2");
-    
-    //retrieve all min/max counts of the possible attributes
-    var minMaxCounts = db.table(usageCondition.buildSql(
-          "select AB_ATTRIBUTEID, ATTRIBUTE_NAME, MIN_COUNT, MAX_COUNT from AB_ATTRIBUTEUSAGE "
-        + "join AB_ATTRIBUTE on AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID", "1=2"
-    ));
-        
-    var validationMessage = [];
-    minMaxCounts.forEach(function ([attributeId, name, minCount, maxCount])
-    {
-        let count = this[attributeId] || 0;
-        //compares the actual usage with the min and max count and generates a message if the usage is too low or too high
-        if (count < minCount)
-            validationMessage.push(translate.withArguments("Attribute \"%0\" has to be used at least %1.", [name, _getTranslatedCount(minCount)]));
-        if (maxCount && count > maxCount)
-            validationMessage.push(translate.withArguments("Attribute \"%0\" can't be used more than %1.", [name, _getTranslatedCount(maxCount)]));
-    }, countObj);
-    
-    return validationMessage.join("\n");
-    
-    //returns the correct count expression by chossing either singular (1 time) or plural (2 times)
-    function _getTranslatedCount (pCount)
-    {
-        if (pCount == 1)
-            return pCount + " " + translate.text("${COUNT_PREPOSITION_SINGLE}");
-        return pCount + " " + translate.text("${COUNT_PREPOSITION_MULTIPLE}");
-    }
-}
-
-/**
- * counts attribute relations
- * 
- * @param {String} pRowId the row id of the entity
- * @param {String} [pObjectType=null] the object type
- * @param {Object} [pAttributeChanges=null] object containing changes and deletions of attributes
- *                          structure = {attributeRelationId : new attributeId or "" when deleted}
- * 
- * @return {Object} object with attribute ids and the count of the usage (without new rows)
- */
-AttributeRelationUtils.countAttributeRelations = function (pRowId, pObjectType, pAttributeChanges)
-{
-    var countObj = {};
-    var condition = SqlCondition.begin()
-        .andPrepare("AB_ATTRIBUTERELATION.OBJECT_ROWID", pRowId);
-    if (pObjectType)
-        condition.andPrepare("AB_ATTRIBUTERELATION.OBJECT_TYPE", pObjectType);
-    var attributeRelations = db.table(condition.buildSql(
-        "select AB_ATTRIBUTERELATIONID, AB_ATTRIBUTE_ID from AB_ATTRIBUTERELATION",
-        "1=2"
-    ));
-    attributeRelations.forEach(function ([relationId, attributeId])
-    { 
-        if (pAttributeChanges && relationId in pAttributeChanges)
-            attributeId = pAttributeChanges[relationId];
-        if (attributeId !== "")
-            this[attributeId] = (this[attributeId] || 0) + 1;
-    }, countObj);
-    
-    return countObj;
-}
-
-/*********************************************************************************************************************/
-
-
-/**
- * Object for the enumeration and management of all attribute types.
- * This Object is only for the general definition of attribute types and for getting
- * data about every type, anything that has to do with a specific attribute (= the function requires an attribute id)
- * should be done in AttributeUtils.
- * The values for each type are:
- * 
- * keyword = the key of the corresponding keyword
- * contentType = the value that is returned in the contentType process for the attribute
- * databaseField = the database field that holds values of attributes with the type
- * 
- * The display name is controlled by the keyword 'AttributeType'
- */
-function $AttributeTypes () {}
-
-$AttributeTypes.TEXT = { 
-    toString : function () {return this.keyword},
-    keyword : "TEXT",
-    contentType : "TEXT", 
-    databaseField : "CHAR_VALUE"
-};
-$AttributeTypes.DATE = {
-    toString : function () {return this.keyword},
-    keyword : "DATE",
-    contentType : "DATE", 
-    databaseField : "DATE_VALUE",
-    getViewValue : function (pValue)
-        {
-            return datetime.toDate(pValue, translate.text("dd.MM.yyyy"));
-        }
-};
-$AttributeTypes.NUMBER = {
-    toString : function () {return this.keyword},
-    keyword : "NUMBER",
-    contentType : "NUMBER", 
-    databaseField : "NUMBER_VALUE"
-};
-$AttributeTypes.BOOLEAN = {
-    toString : function () {return this.keyword},
-    keyword : "BOOLEAN",
-    contentType : "BOOLEAN", 
-    databaseField : "INT_VALUE",
-    getViewValue : function (pValue)
-        {
-            return pValue == "1" ? translate.text("Yes") : translate.text("No");
-        }
-};
-$AttributeTypes.COMBO = {
-    toString : function () {return this.keyword},
-    keyword : "COMBO",
-    contentType : "UNKNOWN",
-    databaseField : "ID_VALUE",
-    isGroup : true
-};
-$AttributeTypes.COMBOVALUE = {
-    toString : function () {return this.keyword},
-    keyword : "COMBOVALUE",
-    contentType : null, 
-    databaseField : null
-};
-$AttributeTypes.GROUP = {
-    toString : function () {return this.keyword},
-    keyword : "GROUP",
-    contentType : null, 
-    databaseField : null,
-    isGroup : true
-};
-$AttributeTypes.KEYWORD = {
-    toString : function () {return this.keyword},
-    keyword : "KEYWORD",
-    contentType : "UNKNOWN", 
-    databaseField : "ID_VALUE", 
-    getViewValue : function (pValue, pKeyword)
-        {
-            return KeywordUtils.getViewValue(pKeyword, pValue);
-        }
-};
-$AttributeTypes.VOID = {
-    toString : function () {return this.keyword},
-    keyword : "VOID",
-    contentType : null,
-    databaseField : null,
-    isGroup : true
-};
-$AttributeTypes.MEMO = { 
-    toString : function () {return this.keyword},
-    keyword : "MEMO",
-    contentType : "LONG_TEXT", 
-    databaseField : "CHAR_VALUE"
-};
-
-function AttributeTypeUtil () {}
-
-/**
- * returns the required contentType for the given attribute type
- * 
- * @param {String} pAttributeType the attribute type 
- *                  (use the values of the AttributeTypes object, e. g. AttributeTypes.TEXT)
- * @return {String} the contentType for the attribute
- */
-AttributeTypeUtil.getContentType = function (pAttributeType)
-{
-    if (pAttributeType in $AttributeTypes)
-        return $AttributeTypes[pAttributeType].contentType;
-    return null;
-}
-
-/**
- * returns if the type is a group type
- * 
- * @param {String} pAttributeType the attribute type 
- *                  (use the values of the AttributeTypes object, e. g. AttributeTypes.TEXT)
- * @return {Boolean} if the type is a group type
- */
-AttributeTypeUtil.isGroupType = function (pAttributeType)
-{
-    if (pAttributeType in $AttributeTypes)
-        return $AttributeTypes[pAttributeType].isGroup || false;
-    return null;
-}
-
-/**
- * returns the database field for the given attribute type that holds the value of the attribute
- * 
- * @param {String} pAttributeType the attribute type 
- *                  (use the values of the AttributeTypes object, e. g. AttributeTypes.TEXT)
- * @return {String} the database field for the attribute
- */
-AttributeTypeUtil.getDatabaseField = function (pAttributeType)
-{
-    if (pAttributeType in $AttributeTypes)
-        return $AttributeTypes[pAttributeType].databaseField;
-    return null;
-}
-
-AttributeTypeUtil.getAttributeViewValue = function (pAttributeType, pValue, pKeyword)
-{
-    if (pAttributeType in $AttributeTypes && "getViewValue" in $AttributeTypes[pAttributeType])
-        return $AttributeTypes[pAttributeType].getViewValue(pValue, pKeyword);
-    return pValue;
-}
-
-AttributeTypeUtil._initTypeColumnData = function ()
-{
-    var columns = [];
-    var typeColumnMap = {};
-    for (let type in $AttributeTypes)
-    {
-        type = $AttributeTypes[type];
-        if (type.databaseField)
-        {
-            var typeKey = type.toString();
-            var colIndex = columns.indexOf(type.databaseField);
-            if (colIndex == -1)
-            {
-                colIndex = columns.length;
-                columns.push(type.databaseField);
-            }
-            typeColumnMap[typeKey] = colIndex;
-        }
-    }
-    this._allDBColumns = columns;
-    this._typeColumnMap = typeColumnMap;
-}
-
-AttributeTypeUtil.getAllDatabaseFields = function ()
-{
-    if (this._allDBColumns == undefined)
-        AttributeTypeUtil._initTypeColumnData();
-    return this._allDBColumns;
-}
-
-AttributeTypeUtil.getTypeColumnIndex = function (pAttributeType)
-{
-    if (this._typeColumnMap == undefined)
-        AttributeTypeUtil._initTypeColumnData();
-    return this._typeColumnMap[pAttributeType.trim()];
-}
-
-/*********************************************************************************************************************/
-
-/**
- * Functions for AttributeUsages.
- * Do not instanciate this!
- * 
- * @class
- */
-function AttributeUsageUtil () {}
-
-/**
- * Creates AttributeUsages for all subordinate attributes of an attribute.
- * This is required when an usage is added to a superordinate attribute.
- * 
- * @param {String} pAttributeId the id of the superordinate attribute
- * @param {String} pObjectType the context
- */
-AttributeUsageUtil.insertChildrenUsages = function (pAttributeId, pObjectType)
-{
-    var table = "AB_ATTRIBUTEUSAGE";
-    var columns = ["AB_ATTRIBUTEUSAGEID", "AB_ATTRIBUTE_ID", "OBJECT_TYPE"];
-    var types = db.getColumnTypes(table, columns);
-    
-    var sqlSelect = "select AB_ATTRIBUTEID, "
-            + " exists (select AB_ATTRIBUTEUSAGEID from AB_ATTRIBUTEUSAGE where AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID and OBJECT_TYPE = '" 
-            + pObjectType + "') from AB_ATTRIBUTE";
-    
-    var inserts = [];
-    _addInserts(pAttributeId, pObjectType);
-    db.inserts(inserts);
-    
-    function _addInserts (pAttributeId, pObjectType)
-    {
-        var condition = SqlCondition.begin()
-            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# != ?")
-            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
-        var attributes = db.table(condition.buildSql(sqlSelect));
-        
-        attributes.forEach(function (row)
-        {
-            if (row[1] != "true")
-            {
-                let values = [util.getNewUUID(), row[0], pObjectType];
-                inserts.push([table, columns, types, values]);
-            }
-            _addInserts(row[0], pObjectType);
-        });
-    }
-}
-
-/**
- * Updates AttributeUsages for all subordinate attributes of an attribute.
- * This is required when an usage of a superordinate attribute is changed.
- * 
- * @param {String} pAttributeId the id of the superordinate attribute
- * @param {String} pOldObjectType ye olde context
- * @param {String} pNewObjectType the new context
- */
-AttributeUsageUtil.updateChildrenUsages = function (pAttributeId, pOldObjectType, pNewObjectType)
-{
-    if (!pNewObjectType)
-        return;
-    
-    var table = "AB_ATTRIBUTEUSAGE";
-    
-    var sqlSelect = "select AB_ATTRIBUTEID, AB_ATTRIBUTEUSAGEID, "
-        + " exists (select AB_ATTRIBUTEUSAGEID from AB_ATTRIBUTEUSAGE where AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID and OBJECT_TYPE = '" 
-        + pNewObjectType + "')"
-        + " from AB_ATTRIBUTE left join AB_ATTRIBUTEUSAGE on AB_ATTRIBUTEID = AB_ATTRIBUTE_ID and OBJECT_TYPE = '" + pOldObjectType + "'";
-    
-    var updateCond = SqlCondition.begin();
-    
-    //it is possible that the new objectType is already in a subordinate attribute 
-    //and an update could cause a duplicate entry so one has to be deleted
-    var deleteCond = SqlCondition.begin();
-    
-    _addUpdateIds(pAttributeId, pOldObjectType);
-        
-    if (updateCond.isSet())
-        db.updateData(table, ["OBJECT_TYPE"], null, [pNewObjectType], updateCond.build("1=2"));
-    if (deleteCond.isSet())
-        db.deleteData(table, deleteCond.build("1=2"));
-    
-    function _addUpdateIds (pAttributeId)
-    {
-        var condition = SqlCondition.begin()
-            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# != ?")
-            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
-        var attributes = db.table(condition.buildSql(sqlSelect));
-        
-        attributes.forEach(function (row)
-        {
-            if (row[1] && row[2] == "true")
-                deleteCond.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[1]);
-            else if (row[1])
-                updateCond.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[1]);
-            _addUpdateIds(row[0]);
-        });
-    }
-}
-
-/**
- * Deletes AttributeUsages for all subordinate attributes of an attribute.
- * This is required when an usage is removed from a superordinate attribute.
- * 
- * @param {String} pAttributeId the id of the superordinate attribute
- * @param {String} pObjectType the context
- */
-AttributeUsageUtil.deleteChildrenUsages = function (pAttributeId, pObjectType)
-{
-    var table = "AB_ATTRIBUTEUSAGE";
-    
-    var sqlSelect = "select AB_ATTRIBUTEID, AB_ATTRIBUTEUSAGEID "
-        + " from AB_ATTRIBUTE left join AB_ATTRIBUTEUSAGE on AB_ATTRIBUTEID = AB_ATTRIBUTE_ID and OBJECT_TYPE = '" + pObjectType + "'";
-    
-    var deleteCond = SqlCondition.begin();
-    _addDeleteIds(pAttributeId, pObjectType);
-    if (deleteCond.isSet())
-        db.deleteData(table, deleteCond.build("1=2"));
-    
-    function _addDeleteIds (pAttributeId)
-    {
-        var condition = SqlCondition.begin()
-            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# != ?")
-            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
-        var attributes = db.table(condition.buildSql(sqlSelect));
-        
-        attributes.forEach(function (row)
-        {
-            if (row[1])
-                deleteCond.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[1])
-            _addDeleteIds(row[0]);
-        });
-    }
-}
-
-/**
- * Deletes duplicate attribute usages.
- * 
- * @param {String} [pAttributeId=null] attribute id, if omitted, all duplicates will be deleted 
- */
-AttributeUsageUtil.removeDuplicates = function (pAttributeId)
-{
-    var condition = SqlCondition.begin()
-        .and("exists (select AB_ATTRIBUTEUSAGEID from AB_ATTRIBUTEUSAGE AU where AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AU.AB_ATTRIBUTE_ID "
-            + "and AB_ATTRIBUTEUSAGE.OBJECT_TYPE = AU.OBJECT_TYPE and AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID != AU.AB_ATTRIBUTEUSAGEID)");
-    if (pAttributeId)
-        condition.andPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", pAttributeId);
-    
-    var duplicates = db.table(condition.buildSql("select AB_ATTRIBUTEUSAGEID, AB_ATTRIBUTE_ID, OBJECT_TYPE from AB_ATTRIBUTEUSAGE"));
-    var usageObj = {};
-    var deleteCond = SqlCondition.begin();
-    
-    duplicates.forEach(function (row)
-    {
-        if (!(row[1] in this))
-            this[row[1]] = {};
-        if (row[2] in this[row[1]])
-            deleteCond.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[0]);
-        this[row[1]][row[2]] = true;
-    }, usageObj);
-    if (deleteCond.isSet())
-        db.deleteData("AB_ATTRIBUTEUSAGE", deleteCond.build("1=2"));
+import("system.logging");
+import("system.util");
+import("system.datetime");
+import("system.translate");
+import("system.neon");
+import("system.vars");
+import("system.db");
+import("Sql_lib");
+import("Keyword_lib");
+
+/**
+ * Provides functions for the work with attributes, like
+ * listing the available attributes for a context.
+ * Don't instanciate this!
+ * 
+ * @class
+ */
+function AttributeUtil () {}
+
+/**
+ * Gives an array of all available attributes for a context. This is used in the possibleItems
+ * process for the attribute id in AttributeRelation
+ * 
+ * @param {String} pObjectType the object type (= context)
+ * @param {boolean} [pIncludeGroups=false]
+ * @param {String[]} [pFilteredAttributeIds=[]] Whitleist of attribute ids
+ * @param {Object} [pAttributeCount=null] Object with attribute ids and their count
+ * 
+ * @return {String[]} array of attributeIds
+ */
+AttributeUtil.getPossibleAttributes = function (pObjectType, pIncludeGroups, pFilteredAttributeIds, pAttributeCount)
+{
+    if (pObjectType == null || (pFilteredAttributeIds && pFilteredAttributeIds.length == 0))
+        return [];
+    
+    var attrSql = "select AB_ATTRIBUTEID from AB_ATTRIBUTE"
+        + " join AB_ATTRIBUTEUSAGE  on AB_ATTRIBUTEID = AB_ATTRIBUTE_ID";
+    var attrCond = SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
+        .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# <> ?")
+        .and("ATTRIBUTE_ACTIVE = 1");
+    
+    if (pAttributeCount)
+    {
+        for (let attributeId in pAttributeCount)
+        {
+            attrCond.andSqlCondition(
+                SqlCondition.begin()
+                    .orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", attributeId, "# != ?")
+                    .orPrepare("AB_ATTRIBUTEUSAGE.MAX_COUNT", pAttributeCount[attributeId], "# > ?")
+                    .or("AB_ATTRIBUTEUSAGE.MAX_COUNT is null")
+            );
+        }
+    }
+        
+    if (pFilteredAttributeIds)
+    {
+        var filteredIdsCondition = new SqlCondition();
+        var filteredIdChildren = AttributeUtil.getAllChildren(pFilteredAttributeIds);
+        pFilteredAttributeIds.concat(filteredIdChildren).forEach(function(id) 
+        {
+            this.orPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", id);
+        }, filteredIdsCondition);
+
+        attrCond.andSqlCondition(filteredIdsCondition);
+    }
+
+    if (!pIncludeGroups)
+        attrCond.and("ATTRIBUTE_TYPE != '" + $AttributeTypes.GROUP + "'");
+    
+    var attributes = db.array(db.COLUMN, attrCond.buildSql(attrSql));
+    
+    return attributes;
+}
+
+/**
+ * returns the name of an attribute with all parent attribute names
+ * 
+ * @param {String} pAttributeId the id of the attribute
+ * @param {Boolean} [pSimpleName=false] Use only the name of the attribute and not the names of the parents.
+ * 
+ * @return {String} the name of the attribute
+ */
+AttributeUtil.getFullAttributeName = function (pAttributeId, pSimpleName) 
+{
+    if (pSimpleName == undefined)
+        pSimpleName = false;
+    
+    if (!pAttributeId)
+        return "";
+    if (pSimpleName)
+        return AttributeUtil.getSimpleAttributeName(pAttributeId);
+    var attributeNames = [];
+    var attribute;
+    do {
+        attribute = db.array(db.ROW, SqlCondition.begin()
+            .andPrepare(["AB_ATTRIBUTE", "AB_ATTRIBUTEID", "ATTRIBUTE"], pAttributeId)
+            .buildSql("select ATTRIBUTE.ATTRIBUTE_NAME, PARENT1.ATTRIBUTE_NAME, PARENT2.ATTRIBUTE_NAME, PARENT2.ATTRIBUTE_PARENT_ID \n\
+                from AB_ATTRIBUTE ATTRIBUTE \n\
+                left join AB_ATTRIBUTE PARENT1 on ATTRIBUTE.ATTRIBUTE_PARENT_ID = PARENT1.AB_ATTRIBUTEID \n\
+                left join AB_ATTRIBUTE PARENT2 on PARENT1.ATTRIBUTE_PARENT_ID = PARENT2.AB_ATTRIBUTEID")
+        );
+        if (attribute.length > 0)
+        {
+            attributeNames.push(attribute[0]);
+            if (attribute[1])
+                attributeNames.push(attribute[1]);
+            if (attribute[2])
+                attributeNames.push(attribute[2]);
+            pAttributeId = attribute[3];
+        }
+        else
+            pAttributeId = "";
+    } while (pAttributeId);
+    
+    return attributeNames.reverse().join(" / ");
+}
+
+/**
+ * returns the name of an attribute
+ * 
+ * @param {String} pAttributeId the id of the attribute
+ * 
+ * @return {String} the name of the attribute
+ */
+AttributeUtil.getSimpleAttributeName = function (pAttributeId) 
+{
+    var attributeName = db.cell(SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
+        .buildSql("select ATTRIBUTE_NAME from AB_ATTRIBUTE")
+    );
+
+    return attributeName;
+}
+
+/**
+ * returns the ids of all subordinated attributes of an attribute
+ * 
+ * @param {String|Array} pAttributeIds the id(s) of the attribute(s)
+ * 
+ * @result {String[]} array with the ids of every subordinated attribute
+ */
+AttributeUtil.getAllChildren = function (pAttributeIds)
+{
+    var childIds = [];
+    if (typeof(pAttributeIds) == "string")
+        pAttributeIds = [pAttributeIds];
+        
+    while (pAttributeIds.length > 0)
+    {
+        pAttributeIds = db.array(db.COLUMN, SqlCondition.begin()
+            .and("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID in ('" + pAttributeIds.join("','") + "')")
+            .buildSql("select AB_ATTRIBUTEID from AB_ATTRIBUTE")
+        );
+        if (pAttributeIds.length > 0)
+            childIds = childIds.concat(pAttributeIds);
+    }
+    return childIds;
+}
+
+/**
+ * checks if an attribute has attribute relations
+ * 
+ * @param {String} pAttributeId the id of the attribute
+ * 
+ * @result {boolean} true if it has relations
+ */
+AttributeUtil.hasRelations = function (pAttributeId)
+{
+    if (!pAttributeId)
+        return false;
+    return db.cell(SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
+        .buildSql(
+            "select exists ("
+            +    "select AB_ATTRIBUTERELATIONID from AB_ATTRIBUTERELATION "
+            +    "where AB_ATTRIBUTE_ID = AB_ATTRIBUTEID"
+            + ") from AB_ATTRIBUTE", "1=2"
+        )
+    ) == "true";
+}
+
+/**
+ * returns the type of an attribute
+ * 
+ * @param {String} pAttributeId the id of the attribute
+ * 
+ * @result {String} attribute type
+ */
+AttributeUtil.getAttributeType = function (pAttributeId)
+{
+    var attrTypeSelect = "select ATTRIBUTE_TYPE from AB_ATTRIBUTE";
+    attrTypeSelect = SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
+        .buildSql(attrTypeSelect);
+    return db.cell(attrTypeSelect).trim();
+}
+
+/*********************************************************************************************************************/
+
+/**
+ * Provides functions for the work with attributeRelations, getting the value of an attributeRelation for an object.
+ * Don't instanciate this!
+ * 
+ * @class
+ */
+function AttributeRelationUtils () {}
+
+/**
+ * gets the value of an attributeRelation for one dataset (e. g. a person)
+ * 
+ * @param {String} pAttributeId attribute-id
+ * @param {String} pObjectRowId row-id of the dataset
+ * @param {String} [pObjectType=null] object-type
+ * @param {String} [pGetViewValue=false] if true the values are resolved and formatted
+ * 
+ * @return {String|null} the value of the attribute
+ */
+AttributeRelationUtils.getAttribute = function (pAttributeId, pObjectRowId, pObjectType, pGetViewValue)
+{
+    var attrCond = SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTERELATION.OBJECT_ROWID", pObjectRowId)
+        .andPrepare("AB_ATTRIBUTERELATION.AB_ATTRIBUTE_ID", pAttributeId);
+    if (pObjectType != null)
+        attrCond.andPrepare("AB_ATTRIBUTERELATION.OBJECT_TYPE", pObjectType);
+    
+    var defaultFields = [
+        "AB_ATTRIBUTE.ATTRIBUTE_TYPE", 
+        "AB_ATTRIBUTE.KEYWORD_CONTAINER", 
+        "COMBOVAL.ATTRIBUTE_NAME"
+    ];
+    var valueFields = AttributeTypeUtil.getAllDatabaseFields();
+    var attributeSql = attrCond.buildSql("select " + defaultFields.join(", ") + ", " + valueFields.join(", ")
+        + " from AB_ATTRIBUTERELATION join AB_ATTRIBUTE on AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID"
+        + " left join AB_ATTRIBUTE COMBOVAL on " + $AttributeTypes.COMBO.databaseField + " = COMBOVAL.AB_ATTRIBUTEID");
+    
+    var attributeValues = db.array(db.ROW, attributeSql);
+    if (!attributeValues.length)
+        return null;
+    
+    let value = attributeValues[AttributeTypeUtil.getTypeColumnIndex(attributeValues[0]) + defaultFields.length];
+    if (pGetViewValue && attributeValues[1].trim() == $AttributeTypes.COMBO)
+        value = attributeValues[2];
+    else if (pGetViewValue)
+        value = AttributeTypeUtil.getAttributeViewValue(attributeValues[0].trim(), value, attributeValues[1]);
+    
+    return value;
+}
+
+/**
+ * gets all attributes for a dataset
+ * 
+ * @param {String} pObjectRowId object rowid
+ * @param {String} [pObjectType=null] object-type
+ * @param {String} [pUseAttributeIds=false] if true the ids are used instead of the full attribute names
+ * @param {String} [pUseIdValues=false] if true the values are not resolved or formatted
+ * 
+ * @return {String[][]} two-dimensional array a row is [attributeId|attributeName, value]
+ */
+AttributeRelationUtils.getAllAttributes = function (pObjectRowId, pObjectType, pUseAttributeIds, pUseIdValues)
+{
+    var attrCond = SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTERELATION.OBJECT_ROWID", pObjectRowId);
+    if (pObjectType != null)
+        attrCond.andPrepare("AB_ATTRIBUTERELATION.OBJECT_TYPE", pObjectType);
+    
+    var defaultFields = [
+        "AB_ATTRIBUTE_ID", 
+        "AB_ATTRIBUTE.ATTRIBUTE_TYPE", 
+        "AB_ATTRIBUTE.KEYWORD_CONTAINER", 
+        "COMBOVAL.ATTRIBUTE_NAME"
+    ];
+    var valueFields = AttributeTypeUtil.getAllDatabaseFields();
+    var attributeSql = attrCond.buildSql("select " + defaultFields.join(", ") + ", " + valueFields.join(", ")
+        + " from AB_ATTRIBUTERELATION join AB_ATTRIBUTE on AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID"
+        + " left join AB_ATTRIBUTE COMBOVAL on " + $AttributeTypes.COMBO.databaseField + " = COMBOVAL.AB_ATTRIBUTEID");
+    
+    var attributeNameMap = {};
+    var attributeValues = db.table(attributeSql).map(function (row) 
+    {
+        let attribute = row[0];
+        if (!pUseAttributeIds)
+        {
+            if (!(attribute in attributeNameMap))
+                attributeNameMap[attribute] = AttributeUtil.getFullAttributeName(attribute);
+            attribute = attributeNameMap[attribute];
+        }
+        let value = row[AttributeTypeUtil.getTypeColumnIndex(row[1]) + defaultFields.length];
+        if (!pUseIdValues && row[1].trim() == $AttributeTypes.COMBO)
+            value = row[3];
+        else if (!pUseIdValues)
+            value = AttributeTypeUtil.getAttributeViewValue(row[1].trim(), value, row[2]);
+        
+        return [attribute, value];
+    });
+    
+    return attributeValues;
+}
+
+/**
+ * gets the correct attribute value from a map with values depending on the attribute id
+ * 
+ * @param {String} pAttributeId the attribute id
+ * @param {Object} pValueMap a map with the attribute values and the db fields as keys
+ * @param {Boolean} [pGetViewValue=false] if true, get the view value
+ * 
+ * @return {String|null} the value of the attribute or null if the attribute doesn't exist
+ */
+AttributeRelationUtils.selectAttributeValue = function (pAttributeId, pValueMap, pGetViewValue)
+{
+    var sqlSelect = "select ATTRIBUTE_TYPE, KEYWORD_CONTAINER from AB_ATTRIBUTE";
+    var type = db.array(db.ROW, SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
+        .buildSql(sqlSelect)
+    );
+    if (!type.length)
+        return null;
+    
+    type[0] = type[0].trim();
+    var field = AttributeTypeUtil.getDatabaseField(type[0]);
+    var value = pValueMap[field];
+    if (pGetViewValue && type[0] == $AttributeTypes.COMBO)
+    {
+        value = db.cell(SqlCondition.begin()
+            .andPrepare("AB_ATTRIBUTE.AB_ATTRIBUTEID", value)
+            .buildSql("select ATTRIBUTE_NAME from AB_ATTRIBUTE")
+        );
+    }
+    else if (pGetViewValue)
+        value = AttributeTypeUtil.getAttributeViewValue(type[0], value, type[1]);
+    
+    return value;
+}
+
+AttributeRelationUtils.getAttributes = function ()
+{
+    //TODO: implement maybe
+}
+
+/**
+ * sets the value of an attribute for one dataset (e. g. a person)
+ */
+AttributeRelationUtils.setAttribute = function ()
+{
+    //TODO: implement
+}
+
+/**
+ * adds rows for attributes with min_count > 0
+ * 
+ * @param {String} pObjectType the object type
+ * @param {String} pConsumer the name of the attribute relation consumer
+ */
+AttributeRelationUtils.presetMandatoryAttributes = function (pObjectType, pConsumer)
+{
+    var mandatoryAttributes = db.table(
+        SqlCondition.begin()
+            .andPrepare("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# <> ?")
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.GROUP, "# <> ?")
+            .and("ATTRIBUTE_ACTIVE = 1")
+            .and("MIN_COUNT > 0")
+            .buildSql("select AB_ATTRIBUTE_ID, MIN_COUNT from AB_ATTRIBUTEUSAGE "
+                + "join AB_ATTRIBUTE on AB_ATTRIBUTE_ID = AB_ATTRIBUTEID")
+    );
+    mandatoryAttributes.forEach(function (usage)
+    {
+        //adding an attribute more than 20 times would be too much (having a min_count > 20 is very unlikely)
+        for (let i = 0; i < usage[1] && i < 20; i++)
+            neon.addRecord(pConsumer, {
+                "AB_ATTRIBUTE_ID" : usage[0]
+            });
+    });
+}
+
+/**
+ * Checks if the count of the used attributes is valid and returns a message if it's not.
+ * 
+ * @param {String} pRowId the row id of the entity
+ * @param {String} [pObjectType=null] the object type
+ * @param {String} pConsumerField the name of the attribute relation consumer
+ * 
+ * @return {String} the validation message or an empty string if everything is ok
+ */
+AttributeRelationUtils.validateAttributeCount = function (pRowId, pObjectType, pConsumerField)
+{
+    var attributeChanges = {};
+    var deletedRows = vars.get("$field." + pConsumerField + ".deletedRows");
+    var changedRows = vars.get("$field." + pConsumerField + ".changedRows");
+    var insertedRows = vars.get("$field." + pConsumerField + ".insertedRows");
+    logging.log("------------------------");
+    logging.log("insertedRows = " + JSON.stringify(insertedRows, null, " "));
+    logging.log("changedRows = " + JSON.stringify(changedRows, null, " "));
+    logging.log("deletedRows = " + JSON.stringify(deletedRows, null, " "));
+
+
+   if (deletedRows)
+    {
+        deletedRows.forEach(function (row)
+        {
+            this[row.UID] = "";
+        }, attributeChanges);
+    }
+    
+    if (changedRows)
+    {
+        changedRows.forEach(function (row)
+        {
+            this[row.UID] = row.AB_ATTRIBUTE_ID;
+        }, attributeChanges);
+    }
+    
+    //get the current count of usages considering the changes
+    //this will merge the counts of attributeChanges and the already stored attributerelations
+    var countObj = AttributeRelationUtils.countAttributeRelations(pRowId, pObjectType, attributeChanges);
+    logging.log("merged ones (countObj init):" + JSON.stringify(countObj, null, " "));
+    
+    if (insertedRows) //append the new rows
+    {
+        insertedRows.forEach(function (row)
+        {
+            this[row.AB_ATTRIBUTE_ID] = (this[row.AB_ATTRIBUTE_ID] || 0) + 1;
+        }, countObj);
+    }
+    var attributeCondition = SqlCondition.begin();
+    AttributeUtil.getPossibleAttributes(pObjectType).forEach(function (attributeId)
+    {
+        this.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", attributeId);
+    }, attributeCondition);
+    
+    var usageCondition = SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTEUSAGE.OBJECT_TYPE", pObjectType)
+        .andSqlCondition(attributeCondition, "1=2");
+    
+    //retrieve all min/max counts of the possible attributes
+    var minMaxCounts = db.table(usageCondition.buildSql(
+          "select AB_ATTRIBUTEID, ATTRIBUTE_NAME, MIN_COUNT, MAX_COUNT from AB_ATTRIBUTEUSAGE "
+        + "join AB_ATTRIBUTE on AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID", "1=2"
+    ));
+        
+    var validationMessage = [];
+    minMaxCounts.forEach(function ([attributeId, name, minCount, maxCount])
+    {
+        let count = this[attributeId] || 0;
+        //compares the actual usage with the min and max count and generates a message if the usage is too low or too high
+        if (count < minCount)
+            validationMessage.push(translate.withArguments("Attribute \"%0\" has to be used at least %1.", [name, _getTranslatedCount(minCount)]));
+        if (maxCount && count > maxCount)
+            validationMessage.push(translate.withArguments("Attribute \"%0\" can't be used more than %1.", [name, _getTranslatedCount(maxCount)]));
+    }, countObj);
+    
+    return validationMessage.join("\n");
+    
+    //returns the correct count expression by chossing either singular (1 time) or plural (2 times)
+    function _getTranslatedCount (pCount)
+    {
+        if (pCount == 1)
+            return pCount + " " + translate.text("${COUNT_PREPOSITION_SINGLE}");
+        return pCount + " " + translate.text("${COUNT_PREPOSITION_MULTIPLE}");
+    }
+}
+
+/**
+ * counts attribute relations
+ * 
+ * @param {String} pRowId the row id of the entity
+ * @param {String} [pObjectType=null] the object type
+ * @param {Object} [pAttributeChanges=null] object containing changes and deletions of attributes
+ *                          structure = {attributeRelationId : new attributeId or "" when deleted}
+ * 
+ * @return {Object} object with attribute ids and the count of the usage (without new rows)
+ */
+AttributeRelationUtils.countAttributeRelations = function (pRowId, pObjectType, pAttributeChanges)
+{
+    //use cases:
+    //complete new row ==> increase count by 1 for that attribute [this is done in another function]
+    //row removed ==> decrease count by 1
+    //row edit: replace one attribute by another attribute ==> increase the new attribute count and decrease the old attribute count
+    //row edit: replace attribute with no new value ==> decrease count for the old attribute
+    //unchanged (already stored) row ==> increase count 
+    var countObj = {};
+
+    var condition = SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTERELATION.OBJECT_ROWID", pRowId)
+        .andPrepareIfSet("AB_ATTRIBUTERELATION.OBJECT_TYPE", pObjectType);
+
+    var storedAttributeRelations = db.table(condition.buildSql(
+        "select AB_ATTRIBUTERELATIONID, AB_ATTRIBUTE_ID from AB_ATTRIBUTERELATION",
+        "1=2"
+    ));
+
+    storedAttributeRelations.forEach(function ([storedAttrRelationId, storedAttributeId]) {
+        var currentAttributeId = storedAttributeId;
+        //merging the data that is stored in the DB and the provided changes
+        if (pAttributeChanges && storedAttrRelationId in pAttributeChanges)
+            currentAttributeId = pAttributeChanges[storedAttrRelationId];
+        
+        // it doesn't matter if a row has been deleted or if the attribute has been set to "nothing"
+        if (currentAttributeId == "")
+            _decrCount(storedAttributeId);
+        else
+        {
+            _incrCount(currentAttributeId);
+            if (currentAttributeId != storedAttributeId)
+                _decrCount(storedAttributeId);
+        }
+    });
+    
+    function _incrCount(pAttributeId)
+    {
+        if (countObj[pAttributeId])
+            countObj[pAttributeId]++;
+        else
+            countObj[pAttributeId] = 1;
+    }
+    
+    function _decrCount(pAttributeId)
+    {
+        if (countObj[pAttributeId])
+            countObj[pAttributeId]--;
+        else
+            countObj[pAttributeId] = 0;
+    }
+    
+    return countObj;
+}
+
+/*********************************************************************************************************************/
+
+
+/**
+ * Object for the enumeration and management of all attribute types.
+ * This Object is only for the general definition of attribute types and for getting
+ * data about every type, anything that has to do with a specific attribute (= the function requires an attribute id)
+ * should be done in AttributeUtils.
+ * The values for each type are:
+ * 
+ * keyword = the key of the corresponding keyword
+ * contentType = the value that is returned in the contentType process for the attribute
+ * databaseField = the database field that holds values of attributes with the type
+ * 
+ * The display name is controlled by the keyword 'AttributeType'
+ */
+function $AttributeTypes () {}
+
+$AttributeTypes.TEXT = { 
+    toString : function () {return this.keyword},
+    keyword : "TEXT",
+    contentType : "TEXT", 
+    databaseField : "CHAR_VALUE"
+};
+$AttributeTypes.DATE = {
+    toString : function () {return this.keyword},
+    keyword : "DATE",
+    contentType : "DATE", 
+    databaseField : "DATE_VALUE",
+    getViewValue : function (pValue)
+        {
+            return datetime.toDate(pValue, translate.text("dd.MM.yyyy"));
+        }
+};
+$AttributeTypes.NUMBER = {
+    toString : function () {return this.keyword},
+    keyword : "NUMBER",
+    contentType : "NUMBER", 
+    databaseField : "NUMBER_VALUE"
+};
+$AttributeTypes.BOOLEAN = {
+    toString : function () {return this.keyword},
+    keyword : "BOOLEAN",
+    contentType : "BOOLEAN", 
+    databaseField : "INT_VALUE",
+    getViewValue : function (pValue)
+        {
+            return pValue == "1" ? translate.text("Yes") : translate.text("No");
+        }
+};
+$AttributeTypes.COMBO = {
+    toString : function () {return this.keyword},
+    keyword : "COMBO",
+    contentType : "UNKNOWN",
+    databaseField : "ID_VALUE",
+    isGroup : true
+};
+$AttributeTypes.COMBOVALUE = {
+    toString : function () {return this.keyword},
+    keyword : "COMBOVALUE",
+    contentType : null, 
+    databaseField : null
+};
+$AttributeTypes.GROUP = {
+    toString : function () {return this.keyword},
+    keyword : "GROUP",
+    contentType : null, 
+    databaseField : null,
+    isGroup : true
+};
+$AttributeTypes.KEYWORD = {
+    toString : function () {return this.keyword},
+    keyword : "KEYWORD",
+    contentType : "UNKNOWN", 
+    databaseField : "ID_VALUE", 
+    getViewValue : function (pValue, pKeyword)
+        {
+            return KeywordUtils.getViewValue(pKeyword, pValue);
+        }
+};
+$AttributeTypes.VOID = {
+    toString : function () {return this.keyword},
+    keyword : "VOID",
+    contentType : null,
+    databaseField : null,
+    isGroup : true
+};
+$AttributeTypes.MEMO = { 
+    toString : function () {return this.keyword},
+    keyword : "MEMO",
+    contentType : "LONG_TEXT", 
+    databaseField : "CHAR_VALUE"
+};
+
+function AttributeTypeUtil () {}
+
+/**
+ * returns the required contentType for the given attribute type
+ * 
+ * @param {String} pAttributeType the attribute type 
+ *                  (use the values of the AttributeTypes object, e. g. AttributeTypes.TEXT)
+ * @return {String} the contentType for the attribute
+ */
+AttributeTypeUtil.getContentType = function (pAttributeType)
+{
+    if (pAttributeType in $AttributeTypes)
+        return $AttributeTypes[pAttributeType].contentType;
+    return null;
+}
+
+/**
+ * returns if the type is a group type
+ * 
+ * @param {String} pAttributeType the attribute type 
+ *                  (use the values of the AttributeTypes object, e. g. AttributeTypes.TEXT)
+ * @return {Boolean} if the type is a group type
+ */
+AttributeTypeUtil.isGroupType = function (pAttributeType)
+{
+    if (pAttributeType in $AttributeTypes)
+        return $AttributeTypes[pAttributeType].isGroup || false;
+    return null;
+}
+
+/**
+ * returns the database field for the given attribute type that holds the value of the attribute
+ * 
+ * @param {String} pAttributeType the attribute type 
+ *                  (use the values of the AttributeTypes object, e. g. AttributeTypes.TEXT)
+ * @return {String} the database field for the attribute
+ */
+AttributeTypeUtil.getDatabaseField = function (pAttributeType)
+{
+    if (pAttributeType in $AttributeTypes)
+        return $AttributeTypes[pAttributeType].databaseField;
+    return null;
+}
+
+AttributeTypeUtil.getAttributeViewValue = function (pAttributeType, pValue, pKeyword)
+{
+    if (pAttributeType in $AttributeTypes && "getViewValue" in $AttributeTypes[pAttributeType])
+        return $AttributeTypes[pAttributeType].getViewValue(pValue, pKeyword);
+    return pValue;
+}
+
+AttributeTypeUtil._initTypeColumnData = function ()
+{
+    var columns = [];
+    var typeColumnMap = {};
+    for (let type in $AttributeTypes)
+    {
+        type = $AttributeTypes[type];
+        if (type.databaseField)
+        {
+            var typeKey = type.toString();
+            var colIndex = columns.indexOf(type.databaseField);
+            if (colIndex == -1)
+            {
+                colIndex = columns.length;
+                columns.push(type.databaseField);
+            }
+            typeColumnMap[typeKey] = colIndex;
+        }
+    }
+    this._allDBColumns = columns;
+    this._typeColumnMap = typeColumnMap;
+}
+
+AttributeTypeUtil.getAllDatabaseFields = function ()
+{
+    if (this._allDBColumns == undefined)
+        AttributeTypeUtil._initTypeColumnData();
+    return this._allDBColumns;
+}
+
+AttributeTypeUtil.getTypeColumnIndex = function (pAttributeType)
+{
+    if (this._typeColumnMap == undefined)
+        AttributeTypeUtil._initTypeColumnData();
+    return this._typeColumnMap[pAttributeType.trim()];
+}
+
+/*********************************************************************************************************************/
+
+/**
+ * Functions for AttributeUsages.
+ * Do not instanciate this!
+ * 
+ * @class
+ */
+function AttributeUsageUtil () {}
+
+/**
+ * Creates AttributeUsages for all subordinate attributes of an attribute.
+ * This is required when an usage is added to a superordinate attribute.
+ * 
+ * @param {String} pAttributeId the id of the superordinate attribute
+ * @param {String} pObjectType the context
+ */
+AttributeUsageUtil.insertChildrenUsages = function (pAttributeId, pObjectType)
+{
+    var table = "AB_ATTRIBUTEUSAGE";
+    var columns = ["AB_ATTRIBUTEUSAGEID", "AB_ATTRIBUTE_ID", "OBJECT_TYPE"];
+    var types = db.getColumnTypes(table, columns);
+    
+    var sqlSelect = "select AB_ATTRIBUTEID, "
+            + " exists (select AB_ATTRIBUTEUSAGEID from AB_ATTRIBUTEUSAGE where AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID and OBJECT_TYPE = '" 
+            + pObjectType + "') from AB_ATTRIBUTE";
+    
+    var inserts = [];
+    _addInserts(pAttributeId, pObjectType);
+    db.inserts(inserts);
+    
+    function _addInserts (pAttributeId, pObjectType)
+    {
+        var condition = SqlCondition.begin()
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# != ?")
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
+        var attributes = db.table(condition.buildSql(sqlSelect));
+        
+        attributes.forEach(function (row)
+        {
+            if (row[1] != "true")
+            {
+                let values = [util.getNewUUID(), row[0], pObjectType];
+                inserts.push([table, columns, types, values]);
+            }
+            _addInserts(row[0], pObjectType);
+        });
+    }
+}
+
+/**
+ * Updates AttributeUsages for all subordinate attributes of an attribute.
+ * This is required when an usage of a superordinate attribute is changed.
+ * 
+ * @param {String} pAttributeId the id of the superordinate attribute
+ * @param {String} pOldObjectType ye olde context
+ * @param {String} pNewObjectType the new context
+ */
+AttributeUsageUtil.updateChildrenUsages = function (pAttributeId, pOldObjectType, pNewObjectType)
+{
+    if (!pNewObjectType)
+        return;
+    
+    var table = "AB_ATTRIBUTEUSAGE";
+    
+    var sqlSelect = "select AB_ATTRIBUTEID, AB_ATTRIBUTEUSAGEID, "
+        + " exists (select AB_ATTRIBUTEUSAGEID from AB_ATTRIBUTEUSAGE where AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID and OBJECT_TYPE = '" 
+        + pNewObjectType + "')"
+        + " from AB_ATTRIBUTE left join AB_ATTRIBUTEUSAGE on AB_ATTRIBUTEID = AB_ATTRIBUTE_ID and OBJECT_TYPE = '" + pOldObjectType + "'";
+    
+    var updateCond = SqlCondition.begin();
+    
+    //it is possible that the new objectType is already in a subordinate attribute 
+    //and an update could cause a duplicate entry so one has to be deleted
+    var deleteCond = SqlCondition.begin();
+    
+    _addUpdateIds(pAttributeId, pOldObjectType);
+        
+    if (updateCond.isSet())
+        db.updateData(table, ["OBJECT_TYPE"], null, [pNewObjectType], updateCond.build("1=2"));
+    if (deleteCond.isSet())
+        db.deleteData(table, deleteCond.build("1=2"));
+    
+    function _addUpdateIds (pAttributeId)
+    {
+        var condition = SqlCondition.begin()
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# != ?")
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
+        var attributes = db.table(condition.buildSql(sqlSelect));
+        
+        attributes.forEach(function (row)
+        {
+            if (row[1] && row[2] == "true")
+                deleteCond.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[1]);
+            else if (row[1])
+                updateCond.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[1]);
+            _addUpdateIds(row[0]);
+        });
+    }
+}
+
+/**
+ * Deletes AttributeUsages for all subordinate attributes of an attribute.
+ * This is required when an usage is removed from a superordinate attribute.
+ * 
+ * @param {String} pAttributeId the id of the superordinate attribute
+ * @param {String} pObjectType the context
+ */
+AttributeUsageUtil.deleteChildrenUsages = function (pAttributeId, pObjectType)
+{
+    var table = "AB_ATTRIBUTEUSAGE";
+    
+    var sqlSelect = "select AB_ATTRIBUTEID, AB_ATTRIBUTEUSAGEID "
+        + " from AB_ATTRIBUTE left join AB_ATTRIBUTEUSAGE on AB_ATTRIBUTEID = AB_ATTRIBUTE_ID and OBJECT_TYPE = '" + pObjectType + "'";
+    
+    var deleteCond = SqlCondition.begin();
+    _addDeleteIds(pAttributeId, pObjectType);
+    if (deleteCond.isSet())
+        db.deleteData(table, deleteCond.build("1=2"));
+    
+    function _addDeleteIds (pAttributeId)
+    {
+        var condition = SqlCondition.begin()
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# != ?")
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
+        var attributes = db.table(condition.buildSql(sqlSelect));
+        
+        attributes.forEach(function (row)
+        {
+            if (row[1])
+                deleteCond.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[1])
+            _addDeleteIds(row[0]);
+        });
+    }
+}
+
+/**
+ * Deletes duplicate attribute usages.
+ * 
+ * @param {String} [pAttributeId=null] attribute id, if omitted, all duplicates will be deleted 
+ */
+AttributeUsageUtil.removeDuplicates = function (pAttributeId)
+{
+    var condition = SqlCondition.begin()
+        .and("exists (select AB_ATTRIBUTEUSAGEID from AB_ATTRIBUTEUSAGE AU where AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AU.AB_ATTRIBUTE_ID "
+            + "and AB_ATTRIBUTEUSAGE.OBJECT_TYPE = AU.OBJECT_TYPE and AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID != AU.AB_ATTRIBUTEUSAGEID)");
+    if (pAttributeId)
+        condition.andPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", pAttributeId);
+    
+    var duplicates = db.table(condition.buildSql("select AB_ATTRIBUTEUSAGEID, AB_ATTRIBUTE_ID, OBJECT_TYPE from AB_ATTRIBUTEUSAGE"));
+    var usageObj = {};
+    var deleteCond = SqlCondition.begin();
+    
+    duplicates.forEach(function (row)
+    {
+        if (!(row[1] in this))
+            this[row[1]] = {};
+        if (row[2] in this[row[1]])
+            deleteCond.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[0]);
+        this[row[1]][row[2]] = true;
+    }, usageObj);
+    if (deleteCond.isSet())
+        db.deleteData("AB_ATTRIBUTEUSAGE", deleteCond.build("1=2"));
 }
\ No newline at end of file