importPackage(Packages.de.elo.ix.client); //@include lib_Class.js //@include lib_sol.common.SordUtils.js //@include lib_sol.common.UserProfile.js //@include lib_sol.common.Locale.js //@include lib_sol.common.ObjectFormatter.js //@include lib_sol.common.ix.ServiceBase.js var logger = sol.create("sol.Logger", { scope: "sol.common.ix.services.ChildrenDataCollector" }); /** * Collects all child sords for a given parent in an optimized way. * * This should be used by dashboards if a larger amount of Sords should be retrieved. In addition information of * required keyword forms and the user locale is returned. * * Please gather information carefully. Only the required objKeys and sordKeys should be collected by this service due * to performance improvements. e.g. * * Collecting 10,000 invoices for the invoice dashboard takes ~15s and creates ~80MB of traffic using the IX JS-API since * deserializing objects might be expensive. Since only a view information is required this service brings down * the execution time to ~5-6s and reduces data traffic to ~6MB. This is done by creating a minimized json-data structure * with the help of a string builder. * * # Example * * By default StatisticSord formatter is used for generating objects that are optimized for statistical * operations. Refer to sol.common.ObjectFormatter.StatisticSord for more information. * * var result = sol.common.IxUtils.execute("RF_sol_common_services_ChildrenDataCollector", { * parentId: '1213', * objKeys: ["INVOICE_DATE", "INVOICE_AMOUNT"] * }); * * # Result * * { * version: '1', * formatter: 'sol.common.ObjectFormatter.StatisticSord', * locale: { * language: 'de', * dateFormat: 'dd.MM.yyyy', * decimalSeperator: ',', * groupingSeperator: '.' * }, * sords: [{ * id: '5669', * name: 'Invoice 123', * O_INVOICE_DATE: '20151202' * }, { * // more sords ... * }], * docMasks: { * 'Incoming Invoice': { * fields: { * INVOICE_DATE: { name: 'Invoice Date', type: 'text' } * } * } * } * } */ sol.define("sol.common.ix.services.ChildrenDataCollector", { extend: "sol.common.ix.ServiceBase", collectorVersion: "1.00.000", /** * @cfg {String} maskName * Restricts search results to a given keywording mask */ maskName: null, /** * @cfg {String} maskNames * Restricts search results to the given keywording masks */ maskNames: null, /** * @cfg {String} parentId * id of the parent element (guid, objId or archivepath) */ parentId: null, /** * @cfg {int} endLevel * Search child objects up to this level below parentId. * * Please note that recursively collecting child nodes is expensive and should be handled with care. * * A value of 0 or 1 means, that only the sub entries directly under the parent are included. * Set this value to -1, to search over all levels. * In this case the level is internally constrained to 32 to avoid an endless loop, * if the tree under the parent contains recursive references. */ endLevel: 1, /** * @cfg {Boolean} [mainParent=false] * Return no references */ mainParent: false, /** * @cfg {Class} formatter (required) * Sord object formatter implementation. * * e.g. `sol.common.ObjectFormatter.StatisticSord` or `sol.common.ObjectFormatter.TemplateSord` */ formatter: "sol.common.ObjectFormatter.StatisticSord", /** * @cfg {Array} sordKeys * List of required sord keys. * * This only returns the given sord keys. This is required in order to limit traffic for not required keys. * * sordKeys: ["id", "guid", "name", "desc"], */ sordKeys: ["id", "name"], /** * @cfg {Array} objKeys * List of required objKeys. * * This only returns the given objKeys. This is required in order to limit traffic for not required keys. * * objKeys: ["INVOICE_DATE", "INVOICE_AMOUNT"], */ objKeys: [], /** * @cfg {Array} filter * List of metadata that limit search results. * * This limits search results to a given list of metadata. This list is passed as an FindByIndex ObjKey-Array while * collecting search results. * * filter: [{ key: 'INVOICE_DATE', val: 'x*' }], * */ filter: [], /** * @cfg {Boolean} [returnDataDefinition=false] * If `true`, the service returns the data definition for the object, which is necessary for the convertion to XML. */ returnDataDefinition: false, /** * @cfg {Boolean} [addSordTypeKind=false] * If `true`, the service returns the kind of the sord type, e.g. "REPOSITORY", "FOLDER" or "DOCUMENT". */ addSordTypeKind: false, /** * @cfg {Boolean} [onlyFolders=undefined] * Restricts search to include folders. * If true and onlyDocuments is false or unset, only folders will be returned. * If true and onlyDocuments is also true, folders and documents will be returned. * If set to false or unset no restriction will be applied. */ onlyFolders: undefined, /** * @cfg {Boolean} [onlyDocuments=undefined] * Restricts search to include documents. * If true and onlyFolders is alse or unset, only documents will be returned. * If true and onlyFolders is also true, documents and folders will be returned. * If set to false or unset no restriction will be applied. */ onlyDocuments: undefined, /** * @property {Object} dataDefinition for xml transformation * This contains the data definition for the result if json objects should be transformed to XML. */ dataDefinition: { rootElementName: "data", dataProperties: ["sord", "sords"], arrayElementTagNames: { sords: "sord" } }, /** * @property {Number} maxFind * Amount of sords that is collected in one step. * This is used by the FindFirstSords, FindNextSords function calls. */ maxFind: 1000, /** * @property {Number} totalCount * The search is terminated if this number of objects is found. If the number of results should not be constrained, * set this value to 232-1 = 2147483647 (maximum value of a positive 32bit integer minus 1). * * Please note that collecting huge amounts of data might lead to performance issues. */ totalCount: 10000, sordKeyMap: { id: { elementSelector: ObjDataC.mbId }, maskName: { elementSelector: ObjDataC.mbMask }, guid: { elementSelector: ObjDataC.mbGuid }, name: { elementSelector: ObjDataC.mbName }, IDateIso: { elementSelector: ObjDataC.mbIDate }, XDateIso: { elementSelector: ObjDataC.mbXDate }, desc: { elementSelector: SordC.mbDesc }, ownerName: { elementSelector: SordC.mbOwnerName } }, initialize: function (config) { var me = this; me.$super("sol.common.ix.ServiceBase", "initialize", [config]); me.maskName = config.maskName || me.maskName; me.parentId = config.parentId || me.parentId; me.formatter = config.formatter || me.formatter; me.sordKeys = config.sordKeys || me.sordKeys; me.objKeys = config.objKeys || me.objKeys; me.hiddenObjKeys = config.hiddenObjKeys || me.hiddenObjKeys; me.hiddenObjKeyPrefix = config.hiddenObjKeyPrefix || me.hiddenObjKeyPrefix; me.allObjKeys = config.allObjKeys || me.allObjKeys; me.ec = config.ec || me.ec; me.endLevel = config.endLevel || me.endLevel; me.returnDataDefinition = config.returnDataDefinition || me.returnDataDefinition; me.addSordTypeKind = config.addSordTypeKind || me.addSordTypeKind; me.onlyFolders = config.onlyFolders; me.onlyDocuments = config.onlyDocuments; me.filter = config.filter || me.filter; me.objKeyMap = {}; me.jsonData = []; me.docMasks = {}; }, /** * Starts the collection of the desired data * @return {Array} */ execute: function () { var me = this; me.rfUtils = sol.common.ix.RfUtils; me.computeSordElementSelector(); me.getTypeConstants(); // add data collector version and formatting information me.jsonData.push("{"); me.jsonData.push('"version":' + JSON.stringify(me.collectorVersion) + ","); me.jsonData.push('"formatter":' + JSON.stringify(me.formatter) + ","); if (me.returnDataDefinition === true) { me.jsonData.push('"dataDefinition":' + JSON.stringify(me.dataDefinition) + ","); } // add locale information me.jsonData.push('"locale":'); me.addLocale(); // add current sord me.collectSord(); // add child sords me.jsonData.push(","); me.jsonData.push('"sords":'); me.collectChildren(); // add doc masks information me.jsonData.push(","); me.jsonData.push('"docMasks":'); me.jsonData.push(JSON.stringify(me.docMasks)); me.jsonData.push("}"); return me.jsonData.join(""); }, /** * @private * * Computes a SordZ selector for information that is required. */ computeSordElementSelector: function () { var me = this, sordKey, elementSelector, i, objKeyName; me.sordZ = new SordZ(ObjDataC.mbMask); if (me.sordKeys) { me.sordKeys.forEach(function (key) { sordKey = me.sordKeyMap[key]; if (sordKey) { elementSelector = sordKey.elementSelector; } if (elementSelector) { me.sordZ.add(elementSelector); } }); } if (me.objKeys) { me.sordZ.add(SordC.mbObjKeys); for (i = 0; i < me.objKeys.length; i++) { objKeyName = me.objKeys[i]; me.objKeyMap[objKeyName] = true; } } }, /** * @private */ collectSord: function () { var me = this, sord, formatter = me.getSordFormatter(), editInfoZ = new EditInfoZ(); if (!me.parentId) { return; } editInfoZ.sordZ = me.sordZ; sord = ixConnect.ix().checkoutSord(me.parentId, editInfoZ, LockC.NO).sord; if (!me.docMasks[sord.maskName]) { me.addDocMaskData(sord.maskName); } me.jsonData.push(","); me.jsonData.push('"sord":'); me.jsonData.push(me.getFormattedJson(formatter, sord, me.docMasks[sord.maskName])); }, /** * @private */ collectChildren: function () { logger.enter("collectChildren"); var me = this, i, filter, objKey, idx, length, formatter = me.getSordFormatter(), jsonData = [], findInfo = new FindInfo(), objKeyFilters = [], findResult; if (me.parentId) { findInfo.findChildren = new FindChildren(); findInfo.findChildren.parentId = me.parentId; findInfo.findChildren.endLevel = me.endLevel; findInfo.findChildren.mainParent = me.mainParent; } // apply optional mask filter if (me.maskName) { if (!findInfo.findByIndex) { findInfo.findByIndex = new FindByIndex(); } findInfo.findByIndex.maskId = me.maskName; } if (me.shouldSearchOnlyFolders()) { me.searchOnlyFolders(findInfo); } if (me.shouldSearchOnlyDocuments()) { me.searchOnlyDocuments(findInfo); } if (me.maskNames) { if (!findInfo.findByIndex) { findInfo.findByIndex = new FindByIndex(); } findInfo.findByIndex.maskIds = me.maskNames; } // apply filter if (me.filter && me.filter.length > 0) { if (!findInfo.findByIndex) { findInfo.findByIndex = new FindByIndex(); } for (i = 0; i < me.filter.length; i++) { filter = me.filter[i]; objKey = new ObjKey(); objKey.name = filter.key; objKey.data = [filter.val]; logger.info("applied filter:", objKey); objKeyFilters.push(objKey); } findInfo.findByIndex.objKeys = objKeyFilters; } // apply find options findInfo.findOptions = new FindOptions(); findInfo.findOptions.totalCount = me.totalCount; try { idx = 0; findResult = ixConnect.ix().findFirstSords(findInfo, me.maxFind, me.sordZ); while (true) { for (i = 0, length = findResult.sords.length; i < length; i++) { if (!me.docMasks[findResult.sords[i].maskName]) { me.addDocMaskData(findResult.sords[i].maskName); } jsonData.push( me.getFormattedJson( formatter, findResult.sords[i], me.docMasks[findResult.sords[i].maskName], { addSordTypeKind: me.shouldIncludeSordType() } )); } if (!findResult.moreResults) { break; } idx += findResult.sords.length; findResult = ixConnect.ix().findNextSords(findResult.searchId, idx, me.maxFind, me.sordZ); } } finally { if (findResult) { ixConnect.ix().findClose(findResult.searchId); } } me.jsonData.push("["); me.jsonData.push(jsonData.join(",")); me.jsonData.push("]"); logger.exit("collectChildren"); }, /** * @private * @return {boolean} */ shouldSearchOnlyFolders: function () { var me = this; return me.onlyFolders; }, /** * @private * @param {de.elo.ix.client.FindInfo} findInfo */ searchOnlyFolders: function (findInfo) { var me = this; findInfo.findByType = findInfo.findByType || new FindByType(); findInfo.findByType.typeStructures = me.onlyFolders; }, /** * @private * @return {boolean} */ shouldSearchOnlyDocuments: function () { var me = this; return me.onlyDocuments; }, /** * @private * @param {de.elo.ix.client.FindInfo} findInfo */ searchOnlyDocuments: function (findInfo) { var me = this; findInfo.findByType = findInfo.findByType || new FindByType(); findInfo.findByType.typeDocuments = me.onlyDocuments; }, /** * @private * @param {Object} formatter * @param {de.elo.ix.client.Sord} sord * @param {String} maskName * @param {Object} params * @return {String} formatted json */ getFormattedJson: function (formatter, sord, maskName, params) { return formatter.buildJson( sord, maskName || null, params || null ); }, /** * @private * @return {boolean} */ shouldIncludeSordType: function () { var me = this; return me.addSordTypeKind; }, /** * @private * @return {Object} */ getSordFormatter: function () { var me = this; return sol.create(me.formatter, { config: { objKeys: me.objKeys, hiddenObjKeys: me.hiddenObjKeys, hiddenObjKeyPrefix: me.hiddenObjKeyPrefix, sordKeys: me.sordKeys } }); }, /** * @private */ addLocale: function () { var me = this; me.userFormats = sol.create("sol.common.Locale", { ec: me.ec }); me.userFormats.read(); me.jsonData.push('{"language": "'); me.jsonData.push(me.userFormats.language); me.jsonData.push('", "dateFormat": "'); me.jsonData.push(me.userFormats.dateFormat); me.jsonData.push('", "decimalSeparator": "'); me.jsonData.push(me.userFormats.decimalSeparator); me.jsonData.push('", "groupingSeparator": "'); me.jsonData.push(me.userFormats.groupingSeparator); me.jsonData.push('"}'); }, /** * @private * Adds document mask data * @param {String} maskName * @return {Object} */ addDocMaskData: function (maskName) { var me = this; if (!me.docMasks[maskName]) { me.docMasks[maskName] = me.buildDocMaskData(maskName); } return me.docMasks[maskName]; }, /** * @private * Builds the document mask data * @param {String} docMaskName Document mask name * @return {Object} */ buildDocMaskData: function (docMaskName) { var me = this, docMask, docMaskData; docMaskData = { fields: {} }; try { docMask = sol.common.SordUtils.getDocMask(docMaskName, me.ec.ci.language); docMask.lines.forEach(function (docMaskLine) { if (me.objKeyMap[docMaskLine.key]) { docMaskData.fields[docMaskLine.key] = { name: String(docMaskLine.name), type: me.docMaskLineTypes[docMaskLine.type] }; } }); } catch (ex) { me.logger.warn(["Can't get mask info: mask={0}, ec.ci.language={1}, exception={2}", docMaskName, me.ec.ci.language, ex]); } return docMaskData; }, /** * @private */ getTypeConstants: function () { var me = this, i, field, docMaskLineC, fields; me.docMaskLineTypes = {}; docMaskLineC = new DocMaskLineC(); fields = docMaskLineC.class.declaredFields; for (i = 0; i < fields.length; i++) { field = fields[i]; field.accessible = true; if (field.name.startsWith("TYPE_")) { me.docMaskLineTypes[String(field.getInt(docMaskLineC))] = String(field.name.substring(5)); } } } }); /** * @member sol.common.ix.services.ChildrenDataCollector * @method RF_sol_common_services_ChildrenDataCollector * @static * @inheritdoc sol.common.ix.ServiceBase#RF_ServiceBaseName */ function RF_sol_common_services_ChildrenDataCollector(ec, configAny) { var config, jsonDataCollector, ecLang, ixConnectLang, result; ecLang = String(ec.ci.language); ixConnectLang = String(ixConnect.loginResult.clientInfo.language); config = sol.common.ix.RfUtils.parseAndCheckParams(ec, arguments.callee.name, configAny), config.ec = ec; log.info("ec.ci.language=" + ecLang); log.info("ixConnect.loginResult.clientInfo.language=" + ixConnectLang); jsonDataCollector = sol.create("sol.common.ix.services.ChildrenDataCollector", config); result = jsonDataCollector.execute(); return result; }