UBDomain.js

var UB = require('./UB');
var _ =  require('./libs/lodash/lodash');

/**
 * @classdesc
 * UnityBase domain object model.
 * Construct new UBDomain instance based on getDomainInfo UB server method result
 *
 * Usage sample:
 *
 *     UBDomain.get('uba_user').attr('name').caption;
 *
 * @class
 * @param {Object} domainInfo getDomainInfo UB server method result
 * @param {Object} domainInfo.domain raw entities collection
 * @param {Object} domainInfo.entityMethods entities methods access rights for current user
 * @param {Object} domainInfo.models information about domain models
 * @param {Object} domainInfo.i18n entities localization to current user language
 * @param {Object} domainInfo.forceMIMEConvertors list of registered server-side MIME converters for document type attribute content
 */
function UBDomain(domainInfo) {
    var me = this,
        entityCodes = Object.keys(domainInfo.domain);
    /**
     * Collection of entities
     * @type {Object<String, UBEntity>}
     */
    this.entities = {};
    entityCodes.forEach(function(entityCode){
        me.entities[entityCode] = new UBEntity(
            domainInfo.domain[entityCode],
            domainInfo.entityMethods[entityCode] || {},
            domainInfo.i18n[entityCode],
            entityCode,
            me
        );
    });

    /**
     * Models collection
     * @type {Object<String, UBModel>}
     */
    this.models = {};
    var modelCodes = Object.keys(domainInfo.models);
    modelCodes.forEach(function(modelCode){
        var m = domainInfo.models[modelCode];
        me.models[modelCode] = new UBModel(m.path, m.needInit, m.needLocalize, m.order);
    });
    /**
     *
     * @type {Object}
     * @readonly
     */
    this.forceMIMEConvertors = domainInfo.forceMIMEConvertors;
}

/**
 * Get entity by code
 * @param {String} entityCode
 * @param {Boolean} [raiseErrorIfNotExists=false] If `true` and entity does not exists throw error
 * @returns {UBEntity}
 */
UBDomain.prototype.get = function(entityCode, raiseErrorIfNotExists){
    var result = this.entities[entityCode];
    if (raiseErrorIfNotExists && !result){
        throw new Error('Entity with code ' + entityCode + ' does not exists.');
    }
    return result;
};

/**
 * Call collBack function for each entity.
 * @param {Function} callBack
 * @param {Object} [scope]
 */
UBDomain.prototype.eachEntity = function(callBack, scope){
    return _.forEach(this.entities, callBack, scope);
};

/**
 * Filter entities by properties
 * @example
 *
 *      // sessionCachedEntites contains all entities with property cacheType equal Session
 *      var sessionCachedEntites = domain.filterEntities({cacheType: 'Session'});
 *
 * @param {Object|Function} config
 * @returns {Array}
 */
UBDomain.prototype.filterEntities = function(config){
    if (_.isFunction(config)){
        return _.filter(this.entities, config);
    } else {
        return _.filter(this.entities, function (item) {
            var res = true;
            for (var prop in config) {
                if (config.hasOwnProperty(prop)) {
                    res = res && (item[prop] === config[prop]);
                }
            }
            return res;
        });
    }
};

/**
 * UnityBase base attribute data types
 * @readonly
 * @enum
 */
UBDomain.ubDataTypes = {
    Many: "Many",
    Entity: "Entity",
    Int: "Int",
    BigInt: "BigInt",
    String: "String",
    Boolean: "Boolean",
    Float: "Float",
    DateTime: "DateTime",
    Date: "Date",
    Currency: "Currency",
    Document: "Document",
    ID: "ID",
    Text: "Text",
    Enum: "Enum"
};

/**
 * UnityBase base mixins
 * @readonly
 * @enum
 */
UBDomain.ubMixins = {
    dataHistory: "dataHistory",
    mStorage: "mStorage",
    unity: "unity",
    treePath: "treePath"
};

/**
 * Service attribute names
 * @readonly
 * @enum
 */
UBDomain.ubServiceFields = {
    dateFrom: "mi_datefrom",
    dateTo: "mi_dateto"
};

/**
 * Return physical type by UBDataType
 * @param {String} dataType
 * @return {String}
 */
UBDomain.getPhysicalDataType = function(dataType) {
    var me = this;

    if (!me.physicalTypeMap) {
        var ubDataTypes = UBDomain.ubDataTypes,
            typeMap = {};

        typeMap[ubDataTypes.Int] = 'int';
        typeMap[ubDataTypes.Entity] = 'int';
        typeMap[ubDataTypes.ID] = 'int';
        typeMap[ubDataTypes.BigInt] = 'int';

        typeMap[ubDataTypes.String] = 'string';
        typeMap[ubDataTypes.Text] = 'string';
        typeMap[ubDataTypes.Enum] = 'string';

        typeMap[ubDataTypes.Float] = 'float';
        typeMap[ubDataTypes.Currency] = 'float';

        typeMap[ubDataTypes.Boolean] = 'boolean';

        typeMap[ubDataTypes.Date] = 'date';
        typeMap[ubDataTypes.DateTime] = 'date';

        me.physicalTypeMap = typeMap;
    }
    return me.physicalTypeMap[dataType] || 'auto';
};

/**
 * Model (logical group of entities)
 * @class
 * @param path
 * @param needInit
 * @param needLocalize
 * @param order
 * @constructor
 */
function UBModel(path, needInit, needLocalize, order){
    this.path = path;
    if (needInit) {
        this.needInit = needInit;
    }
    if (needLocalize) {
        this.needLocalize = needLocalize;
    }
    this.order = order;
}
UBModel.prototype.needInit = false;
UBModel.prototype.needLocalize = false;

/**
 * Collection of attributes
 * @class
 */
function UBEntityAttributes(){}
/**
 * Return a JSON representation of all entity attributes
 * @returns {{}}
 */
UBEntityAttributes.prototype.asJSON = function(){
    var me = this, result = {};
    _.forEach(me, function(prop, propName){
        if (prop.asJSON){
            result[propName] = prop.asJSON();
        } else {
            result[propName] = prop;
        }
    });
    return result;
};

/**
 * @class
 * @param {Object} entityInfo
 * @param {Object} entityMethods
 * @param {Object} i18n
 * @param {String} entityCode
 * @param {UBDomain} domain
 */
function UBEntity(entityInfo, entityMethods, i18n, entityCode, domain){
    var me = this, attributeNames, attributeInfo,
        mixinNames, mixinInfo, i18nMixin;

    /**
     * @type {UBDomain}
     * @readonly
     */
    this.domain = domain;

    /**
     * @type {String}
     * @readonly
     */
    this.code = entityCode;
    //???
    this.autoImplementMixins = true;
    //TODO - write documentation for attributes assigned from entityInfo
    /**
     * Entity model name
     * @property {String} modelName
     * @readonly
     */
    /**
     * @property {String} name
     * @readonly
     */
    /**
     * Internal short alias
     * @property {String} sqlAlias
     * @readonly
     */
    /**
     * Data source connection name
     * @property {String} connectionName
     * @readonly
     */
    _.assign(me, entityInfo);
    if (i18n){
        _.assign(me, i18n);
    }

    attributeNames = Object.keys(entityInfo.attributes || {});
    /**
     * Entity attributes collection
     * @type {Object.<string, UBEntityAttribute>}
     */
    this.attributes = new UBEntityAttributes();
    attributeNames.forEach(function(attributeCode){
        attributeInfo = entityInfo.attributes[attributeCode];
        me.attributes[attributeCode] = new UBEntityAttribute(attributeInfo,
            (i18n && i18n.attributes ? i18n.attributes[attributeCode]: null), attributeCode, me);
    });

    mixinNames = Object.keys(entityInfo.mixins || {});
    /**
     * Collection of entity mixins
     * @type {Object<String, UBEntityMixin>}
     */
    this.mixins = {};
    mixinNames.forEach(function(mixinCode){
        mixinInfo = entityInfo.mixins[mixinCode];
        i18nMixin = (i18n && i18n.mixins ? i18n.mixins[mixinCode]: null);
        switch(mixinCode){
            case 'mStorage':
                me.mixins[mixinCode] = new UBEntityStoreMixin(mixinInfo, i18nMixin, mixinCode);
                break;
            case 'dataHistory':
                me.mixins[mixinCode] = new UBEntityHistoryMixin(mixinInfo, i18nMixin, mixinCode);
                break;
            case 'aclRls':
                me.mixins[mixinCode] = new UBEntityAclRlsMixin(mixinInfo, i18nMixin, mixinCode);
                break;
            case 'fts':
                me.mixins[mixinCode] = new UBEntityFtsMixin(mixinInfo, i18nMixin, mixinCode);
                break;
            case 'als':
                me.mixins[mixinCode] = new UBEntityAlsMixin(mixinInfo, i18nMixin, mixinCode);
                break;
            default:
                me.mixins[mixinCode] = new UBEntityMixin(mixinInfo, i18nMixin, mixinCode);
        }
    });
    /**
     * Entity methods, allowed for current logged-in user in format {method1: 1, method2: 1}. 1 mean method is allowed
     * @type {Object<String, Number>}
     * @readOnly
     */
    this.entityMethods = entityMethods || {};
}


/**
 * Entity caption
 * @type {string}
 */
UBEntity.prototype.caption = '';
/**
 * Entity description
 * @type {string}
 */
UBEntity.prototype.description = '';
/**
 * Documentation
 * @type {string}
 */
UBEntity.prototype.documentation = '';
/**
 * Name of attribute witch used as a display value in lookup
 * @type {string}
 */
UBEntity.prototype.descriptionAttribute = '';
UBEntity.prototype.fullPath = '';
UBEntity.prototype.sqlAlias = '';
/**
 * Indicate how entity content is cached on the client side.
 *
 * Possible cache types: None, "None", "Entity", "Session", "SessionEntity"
 * @type {String}
 * @readonly
 */
UBEntity.prototype.cacheType = 'None';
UBEntity.prototype.dsType = 'Normal';

/**
 * Return an entity caption to display on UI
 * @returns {string}
 */
UBEntity.prototype.getEntityCaption = function() {
    return  this.caption || this.description;
};

/**
 * Get entity attribute by code. Return undefined if attribute does not found
 * @param {String} attributeCode
 * @param {Boolean} [simple] Is do not complex attribute name. By default false.
 * @returns {UBEntityAttribute}
 */
UBEntity.prototype.attr = function(attributeCode, simple){
    if (simple){
        return this.attributes[attributeCode];
    } else {
        return this.getEntityAttribute(attributeCode);
    }
};

/**
 * Get entity attribute by code. Throw error if attribute does not found.
 * @param attributeCode
 * @returns {UBEntityAttribute}
 */
UBEntity.prototype.getAttribute = function(attributeCode){
    var me = this, attr;
    attr = me.attributes[attributeCode];
    if(!attr){
        throw new Error('Attribute ' + me.code + '.' + attributeCode  + ' doesn\'t exist' );
    }
    return attr;
};

/**
 * Call collBack function for each attribute.
 * @param {Function} callBack
 * @param {Object} [scope]
 */
UBEntity.prototype.eachAttribute = function(callBack, scope){
    return _.forEach(this.attributes, callBack, scope);
};

/**
 * Get entity mixin by code. Returns "undefined" if the mixin is not found
 * @param {String} mixinCode
 * @returns {UBEntityMixin}
 */
UBEntity.prototype.mixin = function(mixinCode){
    return this.mixins[mixinCode];
};

/**
 * Check the entity has mixin. Returns `true` if the mixin is exist and enabled
 * @param {String} mixinCode
 * @returns {Boolean}
 */
UBEntity.prototype.hasMixin = function(mixinCode){
    var mixin = this.mixins[mixinCode];
    if ('audit' === mixinCode){
        return !mixin || (!!mixin && mixin.enabled);
    }
    return (!!mixin && mixin.enabled);
};

/**
 * Check the entity has mixin. Throw error if mixin dose not exist or not enabled
 * @param {String} mixinCode
 */
UBEntity.prototype.checkMixin = function(mixinCode){
    if (!this.hasMixin(mixinCode)){
        throw new Error("Entity " + this.code + ' does not have mixin ' + mixinCode);
    }
};

UBEntity.prototype.asJSON = function(){
    var me = this, result = {code: me.code};
    _.forEach(me, function(prop, propName){
        if (propName === 'domain'){
            return;
        }
        if (prop.asJSON){
            result[propName] = prop.asJSON();
        } else {
            result[propName] = prop;
        }
    });
    return result;
};


/**
 * Filter attributes by properties
 * @param {Object|Function} config
 * @returns {Array}
 * example
 *
 *      domain.get('uba_user').filterAttribute({dataType: 'Document'});
 *
 *   return all attributes where property dataType equal Document
 */
UBEntity.prototype.filterAttribute = function(config){
    if (_.isFunction(config)){
        return _.filter(this.attributes, config);
    } else {
        return _.filter(this.attributes, function (item) {
            var res = true;
            for (var prop in config) {
                if (config.hasOwnProperty(prop)) {
                    res = res && (item[prop] === config[prop]);
                }
            }
            return res;
        });
    }
};


/**
 * Check current user have access to specified entity method
 * @param {String} methodCode
 * @returns {Boolean}
 */
UBEntity.prototype.haveAccessToMethod  = function(methodCode){
    return this.entityMethods[methodCode] === 1;
};


/**
 * Check current user have access to AT LAST one of specified methods
 * @param {Array} methods
 * @returns {boolean}
 */
UBEntity.prototype.haveAccessToAnyMethods = function(methods){
    var me = this,
        fMethods = methods || [], result = false;

    fMethods.forEach(function(methodCode){
        result = result || me.entityMethods[methodCode] === 1;
    });
    return result;
};


/**
 * Check current user have access to ALL of specified methods
 * @param {Array<String>} Method names
 * @returns {Boolean}
 */
UBEntity.prototype.haveAccessToMethods = function(methods){
    var me = this,
        result = true,
        fMethods = methods || [];

    fMethods.forEach(function(methodCode){
        result = result && (me.entityMethods[methodCode] === 1);
    });
    return result;
};


/**
 * return array of conversion rules for raw server response data
 * @param {Array<String>} fieldList
 * @returns {Array<{index: number, convertFn: function}>}
 */
UBEntity.prototype.getConvertRules = function(fieldList) {
    var
        me = this,
        rules = [],
        attribute,
        types = UBDomain.ubDataTypes;

    fieldList.forEach(function(fieldName, index){
        attribute = me.attr(fieldName);
        if(attribute) {
            if (attribute.dataType === types.DateTime) {
                rules.push({
                    index: index,
                    convertFn: UB.iso8601Parse
                });
            } else if (attribute.dataType === types.Date){
                rules.push({
                    index: index,
                    convertFn: UB.iso8601ParseAsDate
                });
            } else if (attribute.dataType === types.Boolean) {
                rules.push({
                    index: index,
                    convertFn: UB.booleanParse
                });
            }
        }
    });
    return rules;
};

/**
 * Return description attribute name (`descriptionAttribute` metadata property)
 * This property may be empty or valid(validation performed by server)
 * If case property is empty - try to get attribute with code `caption`
 *
 * @return {String}
 */
UBEntity.prototype.getDescriptionAttribute = function() {
    var result = this.descriptionAttribute || 'caption';
    if (!this.attr(result)){
        throw new Error('Missing description attribute for entity ' + this.code);
    }
    return result;
};


/**
 * Return information about attribute and attribute entity. Understand complex attributes like firmID.firmType.code
 * @param {String} attributeName
 * @param {Number} [deep] If 0 - last, -1 - before last, > 0 - root. Default 0.
 * @return {{ entity: String, attribute: Object, attributeCode: String }}
 */
UBEntity.prototype.getEntityAttributeInfo = function(attributeName, deep) {
    var me = this,
        domainEntity = me,
        attributeNameParts = attributeName.split('.'),
        currentLevel = - (attributeNameParts.length - 1),
        complexAttr = [],
        currentEntity = me.code,
        /** @type UBEntityAttribute  */ attribute, key;

    if (deep && deep > 0){
        return { entity: currentEntity,  attribute: domainEntity.attr(attributeNameParts[0]), attributeCode: attributeNameParts[0]  };
    }

    while(domainEntity && attributeNameParts.length){
        if(domainEntity && attributeNameParts.length === 1 ){
            complexAttr = attributeNameParts[0].split('@');
            if (complexAttr.length > 1){
                domainEntity = me.domain.get(complexAttr[1]); //real entity is text after @
                attributeName = complexAttr[0];
            }
            return { entity: currentEntity,  attribute: domainEntity.attr(attributeName), attributeCode: attributeName };
        }
        key = attributeNameParts.shift();
        complexAttr = key.split('@');
        if (complexAttr.length > 1){
            currentEntity = complexAttr[1];
            domainEntity = me.domain.get(currentEntity); //real entity is text after @
            key = complexAttr[0];
        }
        attribute = domainEntity.attr(key);
        if (attribute){ //check that attribute exists in domainEntity
            if (currentLevel === (deep || 0) ){
                return { entity: currentEntity,  attribute: attribute, attributeCode: key };
            }
            attributeName = attributeNameParts[0];
            if(attribute.dataType === "Enum" && attributeName === 'name'){
                return { entity: currentEntity,  attribute: attribute, attributeCode: key  };
            } else {
                currentEntity = attribute.associatedEntity;
                domainEntity = attribute.getAssociatedEntity();
            }
        } else {
            return undefined;
        }
        currentLevel += 1;
    }
    return undefined;
};

/**
 * Return Entity attribute. Understand complex attributes like firmID.firmType.code
 * @param {String} attributeName
 * @param {Number} [deep] If 0 - last, -1 - before last, > 0 - root. Default 0.
 * @return {UBEntityAttribute}
 */
UBEntity.prototype.getEntityAttribute = function(attributeName, deep) {
    var
        me = this,
        domainEntity = me,
        attributeNameParts = attributeName.split('.'),
        currentLevel = - (attributeNameParts.length - 1),
        complexAttr = [],
        attribute, key;

    if (deep && deep > 0){
        return  domainEntity.attributes[attributeNameParts[0]];
    }

    //TODO: Сделать так же для других спец.символов, кроме @
    while(domainEntity && attributeNameParts.length){
        if(domainEntity && attributeNameParts.length ===1 ){
            complexAttr = attributeNameParts[0].split('@');
            if (complexAttr.length > 1){
                domainEntity = me.domain.get(complexAttr[1]); //real entity is text after @
                attributeName = complexAttr[0];
            }
            return domainEntity.attributes[attributeName];
        }
        key = attributeNameParts.shift();
        complexAttr = key.split('@');
        if (complexAttr.length > 1){
            domainEntity = me.domain.get(complexAttr[1]); //real entity is text after @
            key = complexAttr[0];
        }
        attribute = domainEntity.attributes[key];
        if (attribute){ //check that attribute exists in domainEntity
            if (currentLevel === (deep || 0) ){
                return attribute;
            }
            attributeName = attributeNameParts[0];
            if(attribute.dataType === "Enum" && attributeName === 'name'){
                return attribute;
            } else {
                domainEntity = me.domain.get(attribute.associatedEntity);
            }
        } else {
            return undefined;
        }
        currentLevel += 1;
    }
    return undefined;
};



/**
 * return attributes code list
 * @param {Object|Function} [filter]
 * @returns String[]
 */
UBEntity.prototype.getAttributeNames = function(filter) {
    var attributes = [];
    if (filter){
        _.forEach( this.filterAttribute(filter) , function(attr){
            attributes.push(attr.code);
        });
        return attributes;
    } else {
        return Object.keys(this.attributes);
    }
};

/**
 * Return requirements entity code list for field list
 * @param {String[]} [fieldList] (optional)
 * @return {String[]}
 */
UBEntity.prototype.getEntityRequirements = function(fieldList) {
    var
        me = this,
        result = [],
        fieldNameParts, attr, tail;


    fieldList = fieldList || me.getAttributeNames();

    for(var i = 0, len = fieldList.length; i < len; ++i){
        fieldNameParts = fieldList[i].split('.');

        attr = me.getEntityAttribute(fieldNameParts[0]);
        if(attr.dataType === 'Entity'){
            if(fieldNameParts.length > 1) {
                tail = [fieldNameParts.slice(1).join('.')];
                result = _.union(result, attr.getAssociatedEntity().getEntityRequirements( tail ) );
            } else {
                result = _.union(result, [attr.associatedEntity]);
            }
        }
    }

    return result;
};

/**
 * Check the entity contains attribute(s) and throw error if not contains
 * @param {String|Array<String>} attributeName
 * @param {String} contextMessage
 */
UBEntity.prototype.checkAttributeExist = function(attributeName, contextMessage){
    var me = this;
    attributeName = !_.isArray(attributeName) ? [attributeName]: attributeName;
    _.forEach(attributeName, function(fieldName){
        if (!me.getEntityAttributeInfo(fieldName)){
            throw new Error(contextMessage + (contextMessage ? ' ': '') +
            'The entity "' + me.code + '" does not have attribute "' + fieldName + '"' );
        }
    });
};


/**
 * Return entity description.
 * @returns {string}
 */
UBEntity.prototype.getEntityDescription = function(){
    return this.description || this.caption;
};

/**
 *
 * @param {Object} attributeInfo
 * @param {Object} i18n
 * @param {String} attributeCode
 * @param {UBEntity} entity
 * @constructor
 */
function UBEntityAttribute(attributeInfo, i18n, attributeCode, entity){
    /**
     * @property {String}
     * @readonly
     */
    this.code = attributeCode;

    /**
     * @property {UBEntity}
     * @readonly
     */
    this.entity = entity;

    /**
     * Data type
     * String,   Строка небольшой длины. MSSQL: NVARCHAR, ORACLE: NVARCHAR2, POSTGRE: VARCHAR
     * Int,      32-разрядный Integer. MSSQL: INT, ORACLE: INTEGER, POSTGRE: INTEGER
     * BigInt,   64-разрядный Integer. MSSQL: BIGINT, ORACLE: NUMBER(19), POSTGRE: BIGINT
     * Float,    Double. MSSQL: FLOAT, ORACLE: NUMBER(19, 4), POSTGRE: NUMERIC(19, 4)
     * Currency, Currency(4 знака после запятой). MSSQL: FLOAT, ORACLE: NUMBER(19, 2), POSTGRE: NUMERIC(19, 2)
     * Boolean,  Boolean. MSSQL: TINYINT, ORACLE: NUMBER(1), POSTGRE: SMALLINT
     * DateTime, Дата и время в часовом поясе UTC. MSSQL: DATETIME, OARCLE: DATE, POSTGRE: TIMESTAMP WITH TIME ZONE
     * Text,     Строка большой длины. MSSQL: NVARCHAR(MAX), ORACLE: CLOB, POSTGRE: TEXT
     * ID,       64-разрядный Integer. Типы аналогичны BigInt
     * Entity,   Ссылка на значение ID сущности. Типы аналогичны BigInt
     * Document, Строка длиной 4000 или более. MSSQL: NVARCHAR(MAX), ORACLE: VARCHAR2(4000), POSTGRE: TEXT
     * Many,
     * TimeLog,   Кол-во секунд от X даты, 64-разрядный Integer. MSSQL: BIGINT, ORACLE: NUMBER(19), POSTGRE: BIGINT
     * Enum,      Строка небольшой длины. MSSQL: NVARCHAR, ORACLE: NVARCHAR2, POSTGRE: VARCHAR
     * BLOB,      Бинарные данные. MSSQL: VARBINARY(MAX), ORACLE: BLOB, POSTGRE: BYTEA
     * Date       Дата в локальном часовом поясе.
     * @property {String} dataType
     * @readonly
     */


    /**
     * @property  {String} associatedEntity
     * Название сущности, на которую ссылаемся (для adtMany сущность, на которую ссылаемся из AssociationManyData)
     * @readonly
     */

    /**
     * @property  {String} associationAttr
     * associationAttr
     * @readonly
     */

    /**
     * @property  {String} caption
     * caption
     * @readonly
     */

    /**
     * @property  {String} description
     * description
     * @readonly
     */

    /**
     * @property  {String} documentation
     * documentation
     * @readonly
     */


    /**
     * @property {Number} size
     * size
     * @readonly
     */

    /**
     * @property {boolean} allowNull
     * Can the value of attribute to be null
     * @readonly
     */

    /**
     * @property {boolean} allowSort
     * Allow sort by this attribute
     * @readonly
     */

    /**
     * @property {boolean} isUnique
     * isUnique
     * @readonly
     */

    /**
     * @property {String} defaultValue
     * Default value
     * @readonly
     */

    /**
     * @property {Boolean} readOnly
     * Allow edit the value.
     * @readonly
     */

    /**
     * @property {Boolean} isMultiLang
     * isMultiLang.
     * @readonly
     */

    /**
     * @property {Boolean} cascadeDelete
     * cascadeDelete.
     * Только для атрибутов типа adtEntity! Удалять ли связанные child-записи каскадно
     * @readonly
     */

    /**
     * @property {String} documentMIME
     * documentMIME.
     * @readonly
     */

    /**
     * @property {String} enumGroup
     * enumGroup.
     * @readonly
     */

    /**
     * @property {Object} customSettings
     * Custom settings
     * @readonly
     */

    /**
     * @property {String} associationManyData
     * Table name where stored values for attributes with type Many
     * @readonly
     */

    /**
     * @property {String} storeName
     * Store, where the files are stored (one of the stores from storeConfig of m3Config.json). Used for dataType: Document.
     * If emtpy - server uses store with 'isDefault'=true
     * @readonly
     */

    _.assign(this, attributeInfo);
    if (i18n){
        _.assign(this, i18n);
    }
    /**
     * @property {String} physicalDataType
     * @readonly
     */
    this.physicalDataType = UBDomain.getPhysicalDataType(this.dataType || 'String');
}

// defaults
UBEntityAttribute.prototype.caption = ''; //possible for service(mixin) attributes
UBEntityAttribute.prototype.description = '';
UBEntityAttribute.prototype.documentation = '';
UBEntityAttribute.prototype.size = 0;
UBEntityAttribute.prototype.allowNull = true; // Boolean; { Может ли значение атрибута быть null }
UBEntityAttribute.prototype.allowSort = true; //  Boolean; { Можно ли сортировать по этому атрибуту }
UBEntityAttribute.prototype.isUnique = false;
UBEntityAttribute.prototype.defaultView = true; //  Boolean; { True = атрибут участвует в автопостроителе форм и появляется в запросах '*' }
UBEntityAttribute.prototype.readOnly = false; //  Boolean; { Может ли клиент редактировать этот атрибут }
UBEntityAttribute.prototype.isMultiLang = false;
UBEntityAttribute.prototype.cascadeDelete = false;
UBEntityAttribute.prototype.customSettings = {};
UBEntityAttribute.prototype.dataType = 'String';
/**
 * @property associatedEntity
 * @type {string}
 */
UBEntityAttribute.prototype.associatedEntity = '';


/**
 * Return associated entity. Return null if attribute type is not Entity.
 * @returns {UBEntity}
 */
UBEntityAttribute.prototype.getAssociatedEntity = function(){
     if (this.associatedEntity){
         return this.entity.domain.get(this.associatedEntity);
     }
     return null;
};

UBEntityAttribute.prototype.asJSON = function(){
    var me = this, result = {};
    _.forEach(me, function(prop, propName){
        if (propName === 'entity'){
            return;
        }
        if (prop.asJSON){
            result[propName] = prop.asJSON();
        } else {
            result[propName] = prop;
        }
    });
    return result;
};

/**
 * Contains all properties defined in mixin section of a entity metafile
 * @class
 * @protected
 * @param {Object} mixinInfo
 * @param {Object} i18n
 * @param {String} mixinCode
 */
function UBEntityMixin(mixinInfo, i18n, mixinCode){
    /**
     * Mixin code
     * @type {String}
     */
    this.code = mixinCode;
    _.assign(this, mixinInfo);
    if (i18n){
       _.assign(this, i18n);
    }
}

UBEntityMixin.prototype.enabled = true;

/**
 * Mixin for persisting entity to a database
 * @class
 * @extends UBEntityMixin
 * @param mixinInfo
 * @param i18n
 * @param mixinCode
 */
function UBEntityStoreMixin(mixinInfo, i18n, mixinCode){
     UBEntityMixin.apply(this, arguments);
}
UBEntityStoreMixin.prototype = Object.create(UBEntityMixin.prototype);
UBEntityStoreMixin.prototype.constructor = UBEntityStoreMixin;
// defaults
/**
 * Is `simpleAudit` enabled
 * @type {boolean}
 */
UBEntityStoreMixin.prototype.simpleAudit = false;
/**
 * Use a soft delete
 * @type {boolean}
 */
UBEntityStoreMixin.prototype.safeDelete = false;

/**
 * Historical data storage mixin
 * @class
 * @extends UBEntityMixin
 * @param mixinInfo
 * @param i18n
 * @param mixinCode
 * @constructor
 */
function UBEntityHistoryMixin(mixinInfo, i18n, mixinCode){
     UBEntityMixin.apply(this, arguments);
}
UBEntityHistoryMixin.prototype = Object.create(UBEntityMixin.prototype);
UBEntityHistoryMixin.prototype.constructor = UBEntityHistoryMixin;
/**
 * A history storage strategy
 * @type {string}
 */
UBEntityHistoryMixin.prototype.historyType = 'common';
/**
 * Access control list mixin
 * @class
 * @extends UBEntityMixin
 * @param mixinInfo
 * @param i18n
 * @param mixinCode
 */
function UBEntityAclRlsMixin(mixinInfo, i18n, mixinCode){
    UBEntityMixin.apply(this, arguments);
}
UBEntityAclRlsMixin.prototype = Object.create(UBEntityMixin.prototype);
UBEntityAclRlsMixin.prototype.constructor = UBEntityAclRlsMixin;
// defaults
UBEntityAclRlsMixin.prototype.aclRlsUseUnityName = false;
UBEntityAclRlsMixin.prototype.aclRlsSelectionRule = 'exists';

/**
 * Full text search mixin
 * @class
 * @extends UBEntityMixin
 * @param mixinInfo
 * @param i18n
 * @param mixinCode
 */
function UBEntityFtsMixin(mixinInfo, i18n, mixinCode){
    UBEntityMixin.apply(this, arguments);
}
UBEntityFtsMixin.prototype = Object.create(UBEntityMixin.prototype);
UBEntityFtsMixin.prototype.constructor = UBEntityFtsMixin;
/**
 * scope
 * @type {string}
 */
UBEntityFtsMixin.prototype.scope = 'connection'; //sConnection
/**
 * Data provider type
 * @type {string}
 */
UBEntityFtsMixin.prototype.dataProvider = 'mixin';//dcMixin
/**
 * Attribute level security mixin
 * @param mixinInfo
 * @param i18n
 * @param mixinCode
 * @constructor
 * @extends UBEntityMixin
 */
function UBEntityAlsMixin(mixinInfo, i18n, mixinCode){
    UBEntityMixin.apply(this, arguments);
}
UBEntityAlsMixin.prototype = Object.create(UBEntityMixin.prototype);
UBEntityAlsMixin.prototype.constructor = UBEntityAlsMixin;
/**
 * Is optimistic
 * @type {boolean}
 */
UBEntityAlsMixin.prototype.alsOptimistic = true;

module.exports = UBDomain;