//#import pigeon.js
//#import eventBus.js
//#import Util.js
//#import base64.js
//#import HttpUtil.js
//#import jobs.js
//#import DigestUtil.js
//#import kafkautil.js
//#import @services/dblayer.jsx
//#import @handlers/include/diff.jsx

function trim(s){
    if(s){
        return s.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
    }
    return ""
}

var owl_change_logsService = (function (pigeon) {
    var objPrefix = 'owl_change_logs'
    var listPrefix = 'owl_change_logs'

    var spec = {"id":"string(12),disabled:true;id","nv":"number(10),disabled:true;新版本","ov":"number(10),disabled:true;旧版本","oid":"string(12),disable:true,searchable:true;对象id","ot":"string(12),disable:true;对象类型","ts":"date,disabled:true,searchable:true;发生时间","uid":"string(12),disabled:true;修改者","cli":"choice,values:pcadmin_pc后台/h5admin_h5后台/h5_h5前台/appadmin_app后台/jobs_自动进程;修改方式","jobId":"string(12);jobId","diff":[{"#meta":{"fieldLabel":"修改"},"type":"choice,values:changed_修改/added_添加/deleted;变动类型","fieldKey":"string(12);字段","fieldType":"choice,values:array_列表/other_其他;类型","parentKeys":"array,subtype:string;父字段","oldv":"string(12);旧数据","newv":"string(12);新数据","diff":[{"#meta":{"fieldLabel":"修改"},"type":"choice,values:added_添加/deleted_删除;修改类型","r":"object;数据行"}]}],"_t":"change_logs","#meta":{"push":false,"rem":"修改日志","projectName":"change_logs","parent":["platform"],"visitType":["platform"],"pos":100,"group":{"id":"systemadmin","name":"系统管理","icon":"setting","pos":30},"export":[{"label":"导出完整","mainTitleFormat":{"background":"#474747","color":"#cccccc"},"subTitleFormat":{"background":"#777777","color":"#ffffff"},"subRecordFormat":{"background":"#ffffff","color":"#000000"},"mainRecordFormat":{"background":"#555555","color":"#FFFFFF"},"key":"completeDoc","fields":["*"]}]}}

    var formSpecs = {"meta":{"push":false,"rem":"修改日志","projectName":"change_logs","parent":["platform"],"visitType":["platform"],"pos":100,"group":{"id":"systemadmin","name":"系统管理","icon":"setting","pos":30},"export":[{"label":"导出完整","mainTitleFormat":{"background":"#474747","color":"#cccccc"},"subTitleFormat":{"background":"#777777","color":"#ffffff"},"subRecordFormat":{"background":"#ffffff","color":"#000000"},"mainRecordFormat":{"background":"#555555","color":"#FFFFFF"},"key":"completeDoc","fields":["*"]}]},"_ft":"subForm","fields":[{"fieldType":"string","fieldSize":12,"disabled":"true","fieldLabel":"id","_ft":"field","key":"id","origKey":"id"},{"fieldType":"number","fieldSize":10,"disabled":"true","fieldLabel":"新版本","_ft":"field","key":"nv","origKey":"nv"},{"fieldType":"number","fieldSize":10,"disabled":"true","fieldLabel":"旧版本","_ft":"field","key":"ov","origKey":"ov"},{"fieldType":"string","fieldSize":12,"disable":"true","searchable":"true","fieldLabel":"对象id","_ft":"field","key":"oid","origKey":"oid"},{"fieldType":"string","fieldSize":12,"disable":"true","fieldLabel":"对象类型","_ft":"field","key":"ot","origKey":"ot"},{"fieldType":"date","disabled":"true","searchable":"true","fieldLabel":"发生时间","_ft":"field","key":"ts","origKey":"ts"},{"fieldType":"string","fieldSize":12,"disabled":"true","fieldLabel":"修改者","_ft":"field","key":"uid","origKey":"uid"},{"fieldType":"choice","values":"pcadmin_pc后台/h5admin_h5后台/h5_h5前台/appadmin_app后台/jobs_自动进程","options":[["pcadmin","pc后台"],["h5admin","h5后台"],["h5","h5前台"],["appadmin","app后台"],["jobs","自动进程"]],"fieldLabel":"修改方式","_ft":"field","key":"cli","origKey":"cli"},{"fieldType":"string","fieldSize":12,"fieldLabel":"jobId","_ft":"field","key":"jobId","origKey":"jobId"},{"meta":{"fieldLabel":"修改"},"_ft":"array","fields":[{"fieldType":"choice","values":"changed_修改/added_添加/deleted","options":[["changed","修改"],["added","添加"],["deleted"]],"fieldLabel":"变动类型","_ft":"field","key":"diff.type","origKey":"type"},{"fieldType":"string","fieldSize":12,"fieldLabel":"字段","_ft":"field","key":"diff.fieldKey","origKey":"fieldKey"},{"fieldType":"choice","values":"array_列表/other_其他","options":[["array","列表"],["other","其他"]],"fieldLabel":"类型","_ft":"field","key":"diff.fieldType","origKey":"fieldType"},{"fieldType":"array","subtype":"string","fieldLabel":"父字段","_ft":"field","key":"diff.parentKeys","origKey":"parentKeys"},{"fieldType":"string","fieldSize":12,"fieldLabel":"旧数据","_ft":"field","key":"diff.oldv","origKey":"oldv"},{"fieldType":"string","fieldSize":12,"fieldLabel":"新数据","_ft":"field","key":"diff.newv","origKey":"newv"},{"meta":{"fieldLabel":"修改"},"_ft":"array","fields":[{"fieldType":"choice","values":"added_添加/deleted_删除","options":[["added","添加"],["deleted","删除"]],"fieldLabel":"修改类型","_ft":"field","key":"diff.diff.type","origKey":"type"},{"fieldType":"object","fieldLabel":"数据行","_ft":"field","key":"diff.diff.r","origKey":"r"}],"tab":"99","fieldLabel":"修改","key":"diff.diff","origKey":"diff"}],"tab":"99","fieldLabel":"修改","key":"diff","origKey":"diff"}],"tab":"99","fieldLabel":"请完善资料"}

    var flattenedSpecs = {"mainFields":[{"fieldType":"string","fieldSize":12,"disabled":"true","fieldLabel":"id","_ft":"field","key":"id","origKey":"id"},{"fieldType":"number","fieldSize":10,"disabled":"true","fieldLabel":"新版本","_ft":"field","key":"nv","origKey":"nv"},{"fieldType":"number","fieldSize":10,"disabled":"true","fieldLabel":"旧版本","_ft":"field","key":"ov","origKey":"ov"},{"fieldType":"string","fieldSize":12,"disable":"true","searchable":"true","fieldLabel":"对象id","_ft":"field","key":"oid","origKey":"oid"},{"fieldType":"string","fieldSize":12,"disable":"true","fieldLabel":"对象类型","_ft":"field","key":"ot","origKey":"ot"},{"fieldType":"date","disabled":"true","searchable":"true","fieldLabel":"发生时间","_ft":"field","key":"ts","origKey":"ts"},{"fieldType":"string","fieldSize":12,"disabled":"true","fieldLabel":"修改者","_ft":"field","key":"uid","origKey":"uid"},{"fieldType":"choice","values":"pcadmin_pc后台/h5admin_h5后台/h5_h5前台/appadmin_app后台/jobs_自动进程","options":[["pcadmin","pc后台"],["h5admin","h5后台"],["h5","h5前台"],["appadmin","app后台"],["jobs","自动进程"]],"fieldLabel":"修改方式","_ft":"field","key":"cli","origKey":"cli"},{"fieldType":"string","fieldSize":12,"fieldLabel":"jobId","_ft":"field","key":"jobId","origKey":"jobId"}],"details":[{"name":"diff","fields":[{"fieldType":"choice","values":"changed_修改/added_添加/deleted","options":[["changed","修改"],["added","添加"],["deleted"]],"fieldLabel":"变动类型","_ft":"field","key":"diff.type","origKey":"type"},{"fieldType":"string","fieldSize":12,"fieldLabel":"字段","_ft":"field","key":"diff.fieldKey","origKey":"fieldKey"},{"fieldType":"choice","values":"array_列表/other_其他","options":[["array","列表"],["other","其他"]],"fieldLabel":"类型","_ft":"field","key":"diff.fieldType","origKey":"fieldType"},{"fieldType":"array","subtype":"string","fieldLabel":"父字段","_ft":"field","key":"diff.parentKeys","origKey":"parentKeys"},{"fieldType":"string","fieldSize":12,"fieldLabel":"旧数据","_ft":"field","key":"diff.oldv","origKey":"oldv"},{"fieldType":"string","fieldSize":12,"fieldLabel":"新数据","_ft":"field","key":"diff.newv","origKey":"newv"}],"field":{"meta":{"fieldLabel":"修改"},"_ft":"array","fields":[{"fieldType":"choice","values":"changed_修改/added_添加/deleted","options":[["changed","修改"],["added","添加"],["deleted"]],"fieldLabel":"变动类型","_ft":"field","key":"diff.type","origKey":"type"},{"fieldType":"string","fieldSize":12,"fieldLabel":"字段","_ft":"field","key":"diff.fieldKey","origKey":"fieldKey"},{"fieldType":"choice","values":"array_列表/other_其他","options":[["array","列表"],["other","其他"]],"fieldLabel":"类型","_ft":"field","key":"diff.fieldType","origKey":"fieldType"},{"fieldType":"array","subtype":"string","fieldLabel":"父字段","_ft":"field","key":"diff.parentKeys","origKey":"parentKeys"},{"fieldType":"string","fieldSize":12,"fieldLabel":"旧数据","_ft":"field","key":"diff.oldv","origKey":"oldv"},{"fieldType":"string","fieldSize":12,"fieldLabel":"新数据","_ft":"field","key":"diff.newv","origKey":"newv"},{"meta":{"fieldLabel":"修改"},"_ft":"array","fields":[{"fieldType":"choice","values":"added_添加/deleted_删除","options":[["added","添加"],["deleted","删除"]],"fieldLabel":"修改类型","_ft":"field","key":"diff.diff.type","origKey":"type"},{"fieldType":"object","fieldLabel":"数据行","_ft":"field","key":"diff.diff.r","origKey":"r"}],"tab":"99","fieldLabel":"修改","key":"diff.diff","origKey":"diff"}],"tab":"99","fieldLabel":"修改","key":"diff","origKey":"diff"},"ctx":{"parentField":{"meta":{"fieldLabel":"修改"},"_ft":"array","fields":[{"fieldType":"choice","values":"changed_修改/added_添加/deleted","options":[["changed","修改"],["added","添加"],["deleted"]],"fieldLabel":"变动类型","_ft":"field","key":"diff.type","origKey":"type"},{"fieldType":"string","fieldSize":12,"fieldLabel":"字段","_ft":"field","key":"diff.fieldKey","origKey":"fieldKey"},{"fieldType":"choice","values":"array_列表/other_其他","options":[["array","列表"],["other","其他"]],"fieldLabel":"类型","_ft":"field","key":"diff.fieldType","origKey":"fieldType"},{"fieldType":"array","subtype":"string","fieldLabel":"父字段","_ft":"field","key":"diff.parentKeys","origKey":"parentKeys"},{"fieldType":"string","fieldSize":12,"fieldLabel":"旧数据","_ft":"field","key":"diff.oldv","origKey":"oldv"},{"fieldType":"string","fieldSize":12,"fieldLabel":"新数据","_ft":"field","key":"diff.newv","origKey":"newv"},{"meta":{"fieldLabel":"修改"},"_ft":"array","fields":[{"fieldType":"choice","values":"added_添加/deleted_删除","options":[["added","添加"],["deleted","删除"]],"fieldLabel":"修改类型","_ft":"field","key":"diff.diff.type","origKey":"type"},{"fieldType":"object","fieldLabel":"数据行","_ft":"field","key":"diff.diff.r","origKey":"r"}],"tab":"99","fieldLabel":"修改","key":"diff.diff","origKey":"diff"}],"tab":"99","fieldLabel":"修改","key":"diff","origKey":"diff"}}},{"name":"diff.diff","fields":[{"fieldType":"choice","values":"added_添加/deleted_删除","options":[["added","添加"],["deleted","删除"]],"fieldLabel":"修改类型","_ft":"field","key":"diff.diff.type","origKey":"type"},{"fieldType":"object","fieldLabel":"数据行","_ft":"field","key":"diff.diff.r","origKey":"r"}],"field":{"meta":{"fieldLabel":"修改"},"_ft":"array","fields":[{"fieldType":"choice","values":"added_添加/deleted_删除","options":[["added","添加"],["deleted","删除"]],"fieldLabel":"修改类型","_ft":"field","key":"diff.diff.type","origKey":"type"},{"fieldType":"object","fieldLabel":"数据行","_ft":"field","key":"diff.diff.r","origKey":"r"}],"tab":"99","fieldLabel":"修改","key":"diff.diff","origKey":"diff"},"ctx":{"parentField":{"meta":{"fieldLabel":"修改"},"_ft":"array","fields":[{"fieldType":"choice","values":"added_添加/deleted_删除","options":[["added","添加"],["deleted","删除"]],"fieldLabel":"修改类型","_ft":"field","key":"diff.diff.type","origKey":"type"},{"fieldType":"object","fieldLabel":"数据行","_ft":"field","key":"diff.diff.r","origKey":"r"}],"tab":"99","fieldLabel":"修改","key":"diff.diff","origKey":"diff"}}}]}

    var idFunc = null
    var lockFunc = null

    var f = {
        /**
         * 添加
         */
        getId: function (data) {
            //默认实现是直接返回一个递增的Id
            //其他实现方式包括从data中获取数据构造出一个Id
            if(data.id){
                return data.id;
            }
            //undefined
            if (idFunc) {
                return objPrefix + '_' + idFunc(data)
            }
            else {
                var seq = pigeon.getId(objPrefix)
                return objPrefix + '_' + seq
            }
        },

        getLock: function (data) {
            //undefined
            if (lockFunc) {
                return lockFunc(data)
            }
            else {
                return data['id']
            }
        },

        lock:function(key){
            pigeon.lock(key);
        },
        unlock:function(key){
            pigeon.unlock(key);
        },

        /**
         * 返回一个树状结构
         * 本函数读进所有的数据进内存，在内存中构造一个树状结构
         * 所以对于大的数据不是很合适
         * 适用范围是中小型电商系统的商品分类数，地区管理等
         *
         */
        getTree(){},

        getAllListName: function (data) {
            return listPrefix + '_all'
        },

        getValue: function (fullkey, data) {
            var path = fullkey.split('.')
            var curData = data
            for (var i = 0; i < path.length; i++) {
                var curKey = path[i]
                if (typeof(curData) == 'object' && curData != null) {
                    curData = curData[curKey]
                }
                else {
                    return null
                }
            }
            return curData
        },
        getField:function(fullKey){
            var matchedField=null;
            f.tranverseFields(formSpecs,function(field,ctx){
                if(field.key===fullKey){
                    matchedField = field;
                }
            });
            return matchedField;
        },

        setValue: function (fullkey, value, data) {
            var path = fullkey.split('.')
            var curData = data
            var objPath = [data]
            for (var i = 0; i < path.length; i++) {
                var curKey = path[i]
                if (i == path.length - 1) {
                    curData[curKey] = value
                    return
                }

                var subData = curData[curKey]
                if (!subData) {
                    curData[curKey] = value
                    return
                }
                curData = subData
            }

        },

        getEnvValue: function (v, env) {
            if (!v) {
                return v
            }
            if (v.indexOf('$') == 0) {
                var fullkey = v.substring(1)
                return f.getValue(fullkey, env)
            }
            return v
        },



        getUniqueObj:function(key,value){
            var pigeonKey = objPrefix+ '_' + key + '_' + DigestUtil.md5(value)
            var obj = pigeon.getObject(pigeonKey);
            if(obj==null){
                return null;
            }
            var id = obj.id;
            return pigeon.getObject(id);
        },

        isDuplicated: function (id, key, value) {
            var pigeonKey = objPrefix + '_' + key + '_' + DigestUtil.md5(value)
            var obj = pigeon.getObject(pigeonKey);
            if (obj == null) {
                return false
            }
            if (obj.id === id) {
                return false
            }
            return true
        },



        saveUniqueValue: function (objId, key, value) {
            var md5 = DigestUtil.md5(value)
            var pigeonKey = objPrefix + '_' + key + '_' + md5
            var obj = {
                id: objId,
                key: key,
                value: value,
                md5: md5,
            }
            pigeon.saveObject(pigeonKey, obj)
        },

        removeUniqueValue: function (id, key, value) {
            var md5 = DigestUtil.md5(value)
            var pigeonKey = objPrefix + '_' + key + '_' + md5
            pigeon.saveObject(pigeonKey, null)
        },

        validate: function (data, env) {
            //TODO:需要实现validate 服务器端
            var fields = flattenedSpecs.mainFields
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i]
                var value = f.getValue(field.key, data)
                if(value==null || typeof(value)=='undefined'){
                    value = field.defaultValue;
                    f.setValue(field.key, value, data)
                }
                if (typeof(value) == 'string' && value.indexOf('$') == 0) {
                    value = f.getEnvValue(value, env)
                    f.setValue(field.key, value, data)
                }
                if((value==null || typeof(value)=='undefined') && field.autoGen=='true'){
                    value =$.getUUID()
                    f.setValue(field.key,value,data);
                }
                if ((value==null || typeof(value)=='undefined') && field.required == 'true') {
                    return {'state': 'err', msg: field.fieldLabel + '不能为空。', code: 'required'}
                }

                //对于unique的字段，检查有没有重复
                if (field.unique === 'true') {
                    if (f.isDuplicated(data.id, field.key, value)) {
                        $.log("duplicated,key=" + field.key + ",value=" + value + ",call removeUniqueValue(data.id, field.key, value)");
                        return {'state': 'err', msg: field.fieldLabel + '不能重复。' + value, code: 'duplicated'}
                    }
                }
            }

            var arrFields = flattenedSpecs.details;
            for(var j=0; j<arrFields.length; j++){
                var arrFieldInfo = arrFields[j];
                var rows = f.getValue(arrFieldInfo.name,data);
                if(rows){
                    for(var k=0; k<rows.length; k++){
                        var row = rows[k];
                        for(var l=0; l<arrFieldInfo.fields.length; l++){
                            var subField = arrFieldInfo.fields[l];
                            var value = row[subField.origKey];
                            if(value==null || typeof value=='undefined'){
                                value = subField.defaultValue;
                                row[subField.origKey] = value;
                            }
                            if (typeof(value) == 'string' && value.indexOf('$') == 0) {
                                value = f.getEnvValue(value, env)
                                row[subField.origKey] = value;
                            }
                            if((value==null || typeof(value)=='undefined') && subField.autoGen=='true'){
                                value = $.getUUID()
                                row[subField.origKey] = value;
                            }
                            if ((value==null || typeof(value)=='undefined') && subField.required == 'true') {
                                return {'state': 'err', msg: arrFieldInfo.field.fieldLabel + '.' + subField.fieldLabel + '不能为空。出错的行数为：' + k, code: 'required'}
                            }
                        }
                    }
                }
            }
            return {'state': 'ok'}
        },

        saveUniqueFields: function (data, env) {
            var fields = flattenedSpecs.mainFields
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i]
                //对于unique的字段，检查有没有重复
                if (field.unique === 'true') {
                    var value = f.getValue(field.key, data)
                    if (!value || (typeof(value) == 'string' && value.indexOf('$') == 0)) {
                        value = f.getEnvValue(field.defaultValue, env)
                        if (value) {
                            f.setValue(field.key, value, data)
                        }
                    }
                    if (!value && field.required == 'true') {
                        throw {'state': 'err', msg: field.fieldLabel + '不能为空。', code: 'required'}
                    }

                    if(value && typeof(value)=='string'){
                        if (f.isDuplicated(data.id, field.key, value)) {
                            throw {'state': 'err', msg: field.fieldLabel + '不能重复。', code: 'duplicated'}
                        }

                        //对于没有重复的数据，保存起来
                        f.saveUniqueValue(data.id, field.key, value)
                    }

                }
            }
        },

        removeUniqueFields: function (data, env) {
            var fields = flattenedSpecs.mainFields
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i]
                //对于unique的字段，检查有没有重复
                if (field.unique === 'true') {
                    var value = f.getValue(field.key, data)
                    if (!value || (typeof(value) == 'string' && value.indexOf('$') == 0)) {
                        value = f.getEnvValue(field.defaultValue, env)
                        if (value) {
                            f.setValue(field.key, value, data)
                        }
                    }
                    if(value && typeof(value)=='string'){
                        f.removeUniqueValue(data.id, field.key, value)
                    }
                }
            }
        },

        addToList: function (data) {
            var key = pigeon.getRKey(data['owl_createTime'], 13)
            pigeon.addToList(f.getAllListName(), key, data.id)

            var t = data['owl_createTime']
            var d = new Date(t)
            var year = d.getFullYear()
            var month = d.getMonth() + 1
            var day = d.getDate()

            var listName = listPrefix + '_' + year + '_' + month + '_' + day

            pigeon.addToList(listName, key, data.id)
            //如果这里有子系统的Id则加入子系统list
            if (data['subplatformId']) {
                var listName = listPrefix + '_' + data['subplatformId']
                pigeon.addToList(listName, key, data.id)
            }

            //如果这里有店铺id,则加入店铺list
            if (data['shopId']) {
                var listName = listPrefix + '_' + data['shopId']
                pigeon.addToList(listName, key, data.id)
            }
        },

        tranverseFields: function (formSpec, callback, ctx) {
            formSpec.fields.forEach(function (field) {
                if (field['_ft'] == 'field') {
                    callback(field, ctx)
                }
                else if (field['_ft'] == 'subform') {
                    var context = {parentField: field}
                    f.tranverseFields(field, callback, context)
                }
                else if (field['_ft'] == 'array') {
                    var context = {parentField: field}
                    f.tranverseFields(field, callback, context)
                }
            })
        },

        normalizeValue: function (value, spec) {
            if (value == null) {
                return null
            }
            switch (spec.fieldType) {
                case 'string':
                    return value + ''
                case 'number':
                    if (isNaN(value)) {
                        return null
                    }
                    else {
                        value = Number(value)
                        return value
                    }
                case 'date':
                    return Date(value)
                case 'choice':
                    return value
                default:
                    return value
            }
        },


        getNormalizedDoc: function (data) {
            var obj = JSON.parse(JSON.stringify(data))
            f.tranverseFields(formSpecs, function (field, ctx) {
                if (ctx.parentField && ctx.parentField._ft == 'array') {
                    var items = f.getValue(ctx.parentField.key, obj)
                    if (items) {
                        for (var i = 0; i < items.length; i++) {
                            var item = items[i]
                            var value = item[field.origKey]
                            value = f.normalizeValue(value, field)
                            item[field.origKey] = value
                        }
                    }
                }
                else {
                    var value = f.getValue(field.key, obj)
                    value = f.normalizeValue(value, field)
                    f.setValue(field.key, value, obj)
                }
            }, {})
            return obj
        },

        index: function (doc) {
            var data = f.getNormalizedDoc(doc);
            var m = [];
            m.push(data.m);
            if (data['subplatformId']) {
                m.push(data['subplatformId']);
            }
            if (data['shopId']) {
                m.push(data['shopId'])
            }
            if(data['shop_id']){
                m.push(data['shop_id'])
            }
            data._m = m;
            var elasticSearchUrl = $.getEnv('elasticSearchUrl')

            var headers = {'Content-Type': 'application/json;charset=utf-8'}
            var elasticSearchUser = $.getEnv('elasticSearchUser')
            var elasticSearchPass = $.getEnv('elasticSearchPass')
            if (elasticSearchUser && elasticSearchPass) {
                var auth = Base64.encode(elasticSearchUser + ':' + elasticSearchPass)
                var basicAuth = 'Basic ' + auth
                headers['Authorization'] = basicAuth
            }
            var searchUrl = elasticSearchUrl + '/owl_change_logs/_doc/' + data.id
            var sndTxt = JSON.stringify(data)
            var s = HttpUtils.postRaw(searchUrl, sndTxt, headers)
            s = JSON.parse(s)
            if (!s.result) {
                $.log(data.id + ',index error:' + JSON.stringify(s))
            }
            else {
                // $.log('index ok...')
            }
        },

        updateParentPath(treeNode){
            /*工具类，用于更新对应的parentPathIds和pathPathNames*/
            var parentId = treeNode.parent_id;
            var parentPathIds = [];
            var parentPathNames = [];
            //获得parentId对应的node
            while(parentId && parentId!=='0'){
                var parentNode = f.get(parentId);
                if(parentNode){
                    parentPathIds.push(parentNode.id);
                    parentPathNames.push(parentNode.name);
                    parentId = parentNode.parent_id;
                }
            }

            treeNode.parentPathIds = parentPathIds;
            treeNode.parentPathNames = parentPathNames;

        },
        add: function (data, env) {
            env = env || {}
            var ret = f.validate(data, env)
            if(ret.state!='ok'){
                throw JSON.stringify(ret);
            }

            data.id = f.getId(data)
            data['owl_createTime'] = new Date().getTime();
            data['owl_modifyTime'] = new Date().getTime();
            data['_v'] = 0
            data['_t'] = spec['_t']
            try {
                var meta = spec['#meta'];
                if(meta.isTree){
                    f.updateParentPath(data);
                }
                pigeon.lock(f.getLock(data))
                EventBusService.fire(spec['_t'] + '_add_before', {data: data, env: env})
                f.saveUniqueFields(data, env)
                pigeon.saveObject(data.id, data)
                f.addToList(data)
                f.index(data)

                if(meta && meta.enableJDBC){
                    var sqls = insertObject(data);
                    // $.log(sqls.join(";\n"));
                }
                // $.log("add,meta.push=" +meta.push + ",id=" + data.id);
                if(meta && meta.push){
                    KafkaUtil.send('owlchange',JSON.stringify(data),data.id);
                }
                EventBusService.fire(spec['_t'] + '_add_after', {data: data, env: env})
                return data
            }
            finally {
                pigeon.unlock(f.getLock(data))
            }

        },

        get: function (id,includeDeleted) {
            var meta = spec['#meta'];
            if(meta && meta.push) {
                var cachedObj = appData.getObservableObject(id);
                if (cachedObj) {
                    if (cachedObj.del === 'T' && !includeDeleted) {
                        return null;
                    }
                    return cachedObj;
                }
                var obj = pigeon.getObject(id)
                if(obj){
                    appData.setObservableObject(id,obj);
                }
                else{
                    appData.removeObservableObject(id);
                }

                if (obj && obj.del === 'T' && !includeDeleted) {
                    return null;
                }
                return obj;
            }
            else{
                var obj = pigeon.getObject(id)
                if (obj && obj.del === 'T' && !includeDeleted) {
                    return null;
                }
                return obj;
            }
        },

        getEx:function(id,refresh){
            if(refresh){
                var obj = pigeon.getObject(id);

                if(obj){
                    appData.setObservableObject(id,obj);
                }
                else{
                    appData.removeObservableObject(id);
                }
                if(obj && obj.del==='T'){
                    return null;
                }
                return obj;
            }
            else{
                return f.get(id);
            }
        },

        saveTempData: function (key, data) {
            return pigeon.saveObject(objPrefix + key, data)

        },

        getTempData: function (key) {
            return pigeon.getObject(objPrefix + key)
        },

        getObjects: function (ids) {
            return pigeon.getObjects(ids)
        },

        update: function (data, env) {
            env = env || {}
            var id = data.id

            try {
                var meta = spec['#meta'];
                if(meta.isTree){
                    f.updateParentPath(data);
                }
                pigeon.lock(f.getLock(data))
                var obj = f.get(data.id,true)
                if (!obj) {
                    throw {msg: '对象不存在!id=' + data.id, code: 'notFound'}
                }
                if (obj._v != data._v) {
                    throw {code: 'concurrentupdate', msg: '对象已经修改过，本次修改被拒绝。old._v=' + obj._v + ',new._v=' + data._v}
                }
                var oldObj = JSON.parse(JSON.stringify(obj))
                //深度合并
                //obj = deepMerge(obj,data);//深度合并会引起 编辑出现bug
                obj = data
                obj['_v'] = obj['_v'] + 1
                f.validate(obj, env)
                data._v = obj._v
                obj.owl_modifyTime=new Date().getTime();
                EventBusService.fire(spec['_t'] + '_update_before', {old: oldObj, data: obj, env: env})
                f.addToList(obj)
                f.removeUniqueFields(oldObj, env)
                pigeon.saveObject(id, obj)
                f.saveUniqueFields(data, env)
                f.index(obj)


                var meta = spec['#meta'];
                if(meta && meta.enableJDBC){
                    var sqls = updateObject(obj);
                    // $.log(sqls.join(";\n"));
                }
                if(meta){
                    // $.log("updating meta.push=" + meta.push + ", id=" + obj.id);
                }

                if(meta && meta.push){
                    // $.log("send owlchange," + obj.id)
                    KafkaUtil.send('owlchange',JSON.stringify(obj),obj.id);
                }
                EventBusService.fire(spec['_t'] + '_update_after', {old: oldObj, data: obj, env: env})
                return data
            }
            finally {
                pigeon.unlock(f.getLock(data))
            }
        },

        del: function (id) {
            //只做软删除
            var data = f.get(id,true)
            if (!data) {
                throw '对象不存在!id=' + id
            }
            data.id = id;
            var key = pigeon.getRKey(data['owl_createTime'], 13)
            pigeon.deleteFromList(f.getAllListName(), key, id)
            if (data['subplatformId']) {
                var listName = listPrefix + '_' + data['subplatformId']
                pigeon.deleteFromList(listName, key, id)
            }

            //如果这里有店铺id,则加入店铺list
            if (data['shopId']) {
                var listName = listPrefix + '_' + data['shopId']
                pigeon.deleteFromList(listName, key, id)
            }

            var t = data['owl_createTime']
            var d = new Date(t)
            var year = d.getFullYear()
            var month = d.getMonth() + 1
            var day = d.getDate()

            var listName = listPrefix + '_' + year + '_' + month + '_' + day

            EventBusService.fire(spec['_t'] + '_delete_before', {data: data})
            pigeon.deleteFromList(listName, key, id)

            var deletedList = listPrefix + '_deleted'
            pigeon.addToList(deletedList, key, id)
            data.del = 'T'

            f.index(data)
            pigeon.saveObject(data.id, data)
            f.removeUniqueFields(data, {})
            var meta = spec['#meta'];

            if(meta && meta.enableJDBC){
                var sqls = deleteObject(data);
                // $.log(sqls.join(";\n"));
            }

            if(meta){
                // $.log("del meta.push=" + meta.push + ", id=" + data.id);
            }

            if(meta && meta.push){
                KafkaUtil.send('owlchange',JSON.stringify(data),data.id);
            }
            EventBusService.fire(spec['_t'] + '_delete_after', {data: data})
        },
        getList: function (listName, start, limit) {
            if (!listName) {
                return null
            }
            if (!start) {
                start = 0
            }
            if (!limit) {
                limit = 10
            }
            return pigeon.getListObjects(listName, start, limit)
        },

        getExportRunningList: function () {
            return listPrefix + '_exportRunning'
        },

        getExportFinishedList: function () {
            return listPrefix + '_exportFinished'
        },

        addExportTask: function (query, env) {
            var now = new Date().getTime()
            var taskInfo = {
                loginUser: f.getEnvValue('$loginUser', env),
                submitTime: now,
                startTime: 0,
                processState: 'processing',
                percent: 0,
                _v: 0,
            }
            //这个key如果不多加一个export，会把原有的单据数据覆盖
            var taskInfoId = objPrefix + '_export_' + pigeon.getId()
            var taskId = JobsService.submitExportTask('owl_change_logs', 'tasks/export.jsx', {
                query: query,
                env: env,
                taskInfoId: taskInfoId,
            }, now)
            taskInfo.taskId = '' + taskId
            taskInfo.id = taskInfoId
            var key = pigeon.getRKey(taskInfo.submitTime, 13)
            pigeon.addToList(f.getExportRunningList(), key, taskInfoId)
            pigeon.saveObject(taskInfoId, taskInfo)

            return taskInfoId
        },

        addDocExportTask: function (docId, env) {
            var now = new Date().getTime()
            var taskInfo = {
                loginUser: f.getEnvValue('$loginUser', env),
                submitTime: now,
                startTime: 0,
                processState: 'processing',
                percent: 0,
                total: 1,
                _v: 0,
            }
            //这个key如果不多加一个export，会把原有的单据数据覆盖
            var taskInfoId = objPrefix + '_export_' + pigeon.getId()
            var taskId = JobsService.submitExportTask('owl_change_logs', 'tasks/exportDoc.jsx', {
                id: docId,
                env: env,
                taskInfoId: taskInfoId,
            }, now)
            taskInfo.taskId = '' + taskId
            taskInfo.id = taskInfoId
            var key = pigeon.getRKey(taskInfo.submitTime, 13)
            pigeon.addToList(f.getExportRunningList(), key, taskInfoId)
            pigeon.saveObject(taskInfoId, taskInfo)

            return taskInfoId
        },

        addZipExportTask : function(env){
            var now = new Date().getTime()
            var taskInfo = {
                loginUser: f.getEnvValue('$loginUser', env),
                submitTime: now,
                startTime: 0,
                processState: 'processing',
                percent: 0,
                total: 0,
                _v: 0,
            }
            //这个key如果不多加一个export，会把原有的单据数据覆盖
            var taskInfoId = objPrefix + '_export_' + pigeon.getId()
            var taskId = JobsService.submitExportTask('owl_change_logs', 'tasks/exportZip.jsx', {
                env: env,
                taskInfoId: taskInfoId,
            }, now)
            taskInfo.taskId = '' + taskId
            taskInfo.id = taskInfoId
            var key = pigeon.getRKey(taskInfo.submitTime, 13)
            pigeon.addToList(f.getExportRunningList(), key, taskInfoId)
            pigeon.saveObject(taskInfoId, taskInfo)
            return taskInfoId
        },

        getExportTaskInfo: function (taskInfoId) {
            return pigeon.getObject(taskInfoId)
        },

        updateExportTaskInfo: function (taskInfoId, taskInfo) {
            var oInfo = f.getExportTaskInfo(taskInfoId)
            if (oInfo._v == taskInfo._v) {
                taskInfo._v += 1
                pigeon.saveObject(taskInfoId, taskInfo)
            }
        },


        reindexAll: function () {
            var listName = f.getAllListName()
            var count = pigeon.getListSize(listName)
            $.log("reindexAll " + listName);
            var pos = 0
            while (count > 0) {
                var indexCount = 2000
                if (indexCount > count) {
                    indexCount = count
                }
                var objs = pigeon.getListObjects(listName, pos, indexCount)
                objs.forEach(function (data) {
                    try {
                        f.index(data)
                    } catch (e) {
                        $.log('重建索引异常:' + data.id)
                    }
                })
                pos += indexCount
                count -= indexCount
            }
        },

        getAllSize:function(){
            var listName = f.getAllListName();
            var count = pigeon.getListSize(listName);
            return count;
        },

        getAllObjects:function(){
            var result = [];
            var listName = f.getAllListName()
            var count = pigeon.getListSize(listName)
            var pos = 0
            while (count > 0) {
                var indexCount = 2000
                if (indexCount > count) {
                    indexCount = count
                }
                var objs = pigeon.getListObjects(listName, pos, indexCount)
                result = result.concat(objs);
                pos += indexCount
                count -= indexCount
            }
            return result;
        },

        getAllObjectsEx:function(from,count){
            var listName = f.getAllListName()
            var allsize = pigeon.getListSize(listName)
            var pos = from
            if(count+pos > allsize){
                count = allsize - pos;
            }
            var objs = pigeon.getListObjects(listName, pos, count)
            return objs;
        },

        createQuery:function(searchArgs){
            var filters = [];
            for(var k in searchArgs){
                var v = searchArgs[k];
                if(typeof v === 'object' && v.type === 'or'){
                    if(Array.isArray(v.args)){
                        var shouldClauses = [];
                        v.args.forEach(function(q){
                            var qFilters = f.createQuery(q);
                            if(qFilters.length > 0){
                                shouldClauses.push({
                                    "bool":{
                                        filters:qFilters
                                    }
                                })
                            }
                        });

                        filters.push({bool:{should:shouldClauses}});
                    }
                    else if(v.args && typeof(v.args)=='object'){
                        var shouldClauses = [];
                        var qFilters = f.createQuery(v.args);
                        shouldClauses.push({bool:{filters:qFilters}});
                        filters.push({bool:{should:shouldClauses}});
                    }

                }
                else if(typeof(v)=='object' && Array.isArray(v)){
                    var range={}
                    range[k] = {
                        'gte':v[0],
                        'lte':v[1]
                    }
                    filters.push({range:range});
                }
                else if(typeof(v)=='object' && v.type==='terms'){
                    var terms = {};
                    if(v.values){
                        terms[k + ".keyword"] = v.values;
                        filters.push({terms:terms});
                    }
                }
                else{
                    var term = {};
                    if(typeof v == 'string' ){
                        if(v){
                            term[k+".keyword"] = trim('' + v)
                            filters.push({term:term})
                        }
                    }
                    else if (typeof v == 'number'){
                        term[k] = trim('' + v)
                        filters.push({term:term})
                    }
                }
            }
            return filters;
        },

        buildQuery:function(m,searchArgs,keyword){
            delete searchArgs.keyword;
            var filters = [];
            var must_not = [{
                term:{
                    "del.keyword":'T'
                }
            }];
            for(var k in searchArgs){

                var v = searchArgs[k];

                var isNotClause = false;
                if(k.indexOf("!")===0){
                    isNotClause = true;
                    k = k.substring(1);
                }
                if(typeof v === 'object' && v.type === 'or'){
                    var shouldClauses = [];
                    if(Array.isArray(v.args)){
                        v.args.forEach(function(q){
                            var shouldQueries = f.createQuery(q);
                            if(shouldQueries.length>0){
                                shouldClauses.push({bool:{filter:shouldQueries}});
                            }

                        });
                        if(isNotClause){
                            must_not.push({bool:{should:shouldClauses}});
                        }else{
                            filters.push({bool:{should:shouldClauses}});
                        }

                    }
                    else{
                        if(v.args && typeof(v.args)==='object'){
                            var shouldQueries = f.createQuery(v);
                            if(isNotClause){
                                must_not.push({bool:{should:shouldClauses}});
                            }else{
                                filters.push({bool:{should:shouldClauses}});
                            }
                        }
                    }
                }
                else if(typeof(v)=='object' && Array.isArray(v)){
                    var range={}
                    range[k] = {
                        'gte':v[0],
                        'lte':v[1]
                    }
                    if(isNotClause){
                        must_not.push({range:range});
                    }else{
                        filters.push({range:range});
                    }
                }
                else if(typeof(v)=='object' && v.type==='terms'){
                    var terms = {};
                    if(v.values){
                        terms[k + ".keyword"] = v.values;
                        if(isNotClause){
                            must_not.push({terms:terms});
                        }else{
                            filters.push({terms:terms});
                        }
                    }
                }
                else{
                    var term = {};
                    if(typeof v == 'string' ){
                        if(v){
                            term[k+".keyword"] = trim('' + v)
                            if(isNotClause){
                                must_not.push({term:term});
                            }else{
                                filters.push({term:term});
                            }

                        }
                    }
                    else if (typeof v == 'number'){
                        term[k] = trim('' + v)
                        if(isNotClause){
                            must_not.push({term:term});
                        }else{
                            filters.push({term:term});
                        }
                    }
                }
            }
            if(m !== '0'){
                filters = filters.concat([
                    {"term": { "_m.keyword": m }}
                ]);
            }
            else{
                //如果m === '0'，代表是平台
                // filters = filters.concat([
                //     {"term":{"_t":spec["_t"]}}
                // ]);
            }

            //getKeyword query
            var keywordQuery = "";
            if(keyword && trim(keyword).length>0){
                keywordQuery = "\"" + trim(keyword) + "\""
            }
            else{
                keywordQuery = "*"
            }
            var query = {
                "query": {
                    "bool": {
                        "must": {
                            "query_string": {
                                "query":keywordQuery
                            }
                        },
                        "must_not": must_not,
                        "filter": filters
                    }
                }
            }
            return query;
        },

        count:function(m,searchArgs,keyword,dataSource){
            var query = f.buildQuery(m,searchArgs,keyword);
            var elasticSearchUrl = $.getEnv( "elasticSearchUrl" );

            var headers = { "Content-Type": "application/json;charset=utf-8" };
            var elasticSearchUser = $.getEnv("elasticSearchUser");
            var elasticSearchPass = $.getEnv("elasticSearchPass");
            if(elasticSearchUser && elasticSearchPass){
                var auth =Base64.encode(elasticSearchUser + ":" + elasticSearchPass);
                var basicAuth = "Basic " + auth;
                headers["Authorization"] = basicAuth;
            }
            var searchUrl = elasticSearchUrl+"/owl_change_logs/_count";
            if(dataSource){
                searchUrl = elasticSearchUrl + "/" + dataSource + "/_count";
            }

            var sndTxt = JSON.stringify(query);
            var s = HttpUtils.postRaw( searchUrl, sndTxt, headers);
            var result = JSON.parse(s);
            if(result.count){
                return result.count;
            }
            return 0;
        },
        search: function(m, searchArgs, keyword,from, pageSize, sort, dataSource){
            var query = f.buildQuery(m,searchArgs,keyword);
            var effectiveSort = [{owl_createTime:{order:"desc"}}];
            if(sort){
                effectiveSort = sort;
            } var effectiveSort = [{owl_createTime:{order:"desc"}}];
            if(sort){
                effectiveSort = sort;
            }
            query.from = from;
            query.size = pageSize;
            query.sort = effectiveSort;

            var elasticSearchUrl = $.getEnv( "elasticSearchUrl" );

            var headers = { "Content-Type": "application/json;charset=utf-8" };
            var elasticSearchUser = $.getEnv("elasticSearchUser");
            var elasticSearchPass = $.getEnv("elasticSearchPass");
            if(elasticSearchUser && elasticSearchPass){
                var auth =Base64.encode(elasticSearchUser + ":" + elasticSearchPass);
                var basicAuth = "Basic " + auth;
                headers["Authorization"] = basicAuth;
            }
            var searchUrl = elasticSearchUrl+"/owl_change_logs/_search";
            if(dataSource){
                searchUrl = elasticSearchUrl + "/" + dataSource + "/_search";
            }

            var sndTxt = JSON.stringify(query);
            var s = HttpUtils.postRaw( searchUrl, sndTxt, headers);
            var result = JSON.parse(s);

            if(!result.hits || !result.hits.hits){
                $.log("error search:sndQuery=" + sndTxt + "------\nresponse:\n" + s );
                var ret = {
                    state:'ok',
                    list:[],
                    total:{value:0}
                }
                out.print(JSON.stringify(ret));
                return;
            }

            var hits = result.hits.hits;
            var total = result.hits.total;

            var objs = hits.map(function(hit){return hit._source});


            var ret = {
                state:'ok',
                list:objs,
                total:total
            }

            return ret;

        }
    }
    return f;
})($S);