importPackage(Packages.de.elo.ix.client); //@include lib_Class.js //@include lib_sol.common.RepoUtils.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.SordDataCollector" }); /** * 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_SordDataCollector_FindFirst", { * 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' } * } * } * }, * moreResults: true, * searchId: "(0E9AC245-EB29-AB61-5F81-1D744E00EDB8)" * } * * # Handling paging in search results * * The current search might contain more results that are not returned. The amount of items returned by one search request can be defined by `maxFind`. * A search is automatically closed if all items are returned. If paging is required `moreResults=true` is returned including a `searchId` for upcoming requests. * * moreResults: true, * searchId: "(0E9AC245-EB29-AB61-5F81-1D744E00EDB8)" * * Additional results can be retrieved using a search id and the index. If `idx=50` is passed the search will return the items 50-100 items (as defined by maxFind=50). * * var result = sol.common.IxUtils.execute("RF_sol_common_services_SordDataCollector_FindNext", { * searchId: "(0E9AC245-EB29-AB61-5F81-1D744E00EDB8)", * idx: 50 * }); * * Please note that search requests must be closed if a search id was returned. * * var result = sol.common.IxUtils.execute("RF_sol_common_services_SordDataCollector_FindClose", { * searchId: "(0E9AC245-EB29-AB61-5F81-1D744E00EDB8)" * }); * */ sol.define("sol.common.ix.services.SordDataCollector", { extend: "sol.common.ix.ServiceBase", collectorVersion: "1.00.000", /** * @cfg {String} maskName * Restricts search results to a given keywording mask */ maskName: null, /** * @cfg {Class} formatter (required) * Sord object formatter implementation. * * e.g. `sol.common.ObjectFormatter.StatisticSord` or `sol.common.ObjectFormatter.TemplateSord` */ formatter: "sol.common.ObjectFormatter.TemplateSord", /** * @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", "guid", "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 conversion to XML. */ returnDataDefinition: false, /** * @cfg {String} pageOfObjId Page-of GUID * The returned page will be determinated by the specified object ID */ pageOfObjId: 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: 50, /** * @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, /** * @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 {String} searchMode (INDEX or DIRECT) * Defines the search mode. Available modes are FindByIndex (Mode `INDEX`) or Direct-Search (Mode `Direct`). */ searchMode: "INDEX", /** * @protected */ sordKeyMap: { id: { elementSelector: ObjDataC.mbId }, maskName: { elementSelector: ObjDataC.mbMask }, ownerName: { elementSelector: SordC.mbMinMembers }, guid: { elementSelector: ObjDataC.mbGuid }, name: { elementSelector: ObjDataC.mbName }, IDateIso: { elementSelector: ObjDataC.mbIDate }, XDateIso: { elementSelector: ObjDataC.mbXDate }, desc: { elementSelector: SordC.mbDesc } }, initialize: function (config) { var me = this; me.$super("sol.common.ix.ServiceBase", "initialize", [config]); me.maskName = config.maskName || me.maskName; me.formatter = config.formatter || me.formatter; me.sordKeys = config.sordKeys || me.sordKeys; me.objKeys = config.objKeys || me.objKeys; me.ec = config.ec || me.ec; me.returnDataDefinition = config.returnDataDefinition || me.returnDataDefinition; 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(); if (me.pageOfObjId) { me.determinatePageStartIdxByObjId(); } // add child sords me.collectSords(); // 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 * @param {String} objId Object ID * @param {Number} pageSize Page size */ determinatePageStartIdxByObjId: function () { var me = this, findInfo, ids, page, idxObjId, pageStartIdx; me.objId += ""; findInfo = new FindInfo(); me.buildFindInfo(findInfo); ids = me.findGuids(findInfo); me.pageOfObjId = ixConnect.ix().checkoutSord(me.pageOfObjId, SordC.mbOnlyGuid, LockC.NO).guid + ""; idxObjId = ids.indexOf(me.pageOfObjId); me.jsonData.push(', "pageOfObjId": "' + me.pageOfObjId + '"'); me.jsonData.push(', "idxObjId": ' + idxObjId); me.jsonData.push(', "maxFind": ' + me.maxFind); if (idxObjId < 0) { logger.warn(["Could not find specified objId '{0}' in children requested by user '{1}'", me.pageOfObjId, ixConnect.loginResult.user.name]); page = -1; pageStartIdx = -1; } else { page = Math.floor(idxObjId / me.maxFind); pageStartIdx = page * me.maxFind; } me.idx = pageStartIdx; me.jsonData.push(', "page": ' + page); me.jsonData.push(', "pageStartIdx": ' + pageStartIdx); }, /** * @private * @param {de.elo.ix.client.FindInfo} findInfo Find info * @return {Array} */ findGuids: function (findInfo) { var me = this, idx = 0, ids = [], findResult, i; findResult = ixConnect.ix().findFirstSords(findInfo, 1000, SordC.mbOnlyGuid); ids = []; while (true) { for (i = 0; i < findResult.ids.length; i++) { ids.push(findResult.ids[i] + ""); } if (!findResult.moreResults) { break; } idx += findResult.ids.length; findResult = ixConnect.ix().findNextSords(findResult.searchId, idx, 1000, SordC.mbOnlyGuid); } me.searchId = findResult.searchId + ""; return ids; }, /** * @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 */ collectSords: function () { logger.enter("collectSords"); var me = this, idx, i, length, formatter = me.getSordFormatter(), jsonData = [], jsonSord, findInfo, findResult; if (!me.searchId) { // find first findInfo = new FindInfo(); // apply find options findInfo.findOptions = new FindOptions(); findInfo.findOptions.totalCount = me.totalCount; //findInfo.findOptions.evalCount = true; me.buildFindInfo(findInfo); findResult = ixConnect.ix().findFirstSords(findInfo, me.maxFind, me.sordZ); } else if (me.idx > -1) { idx = me.idx; findResult = ixConnect.ix().findNextSords(me.searchId, idx, me.maxFind, me.sordZ); } if (findResult) { me.jsonData.push(","); if (findResult.fulltextResultItems && findResult.fulltextResultItems.length > 0) { for (i = 0, length = findResult.fulltextResultItems.length; i < length; i++) { if (!me.docMasks[findResult.fulltextResultItems[i].sord.maskName]) { me.addDocMaskData(findResult.fulltextResultItems[i].sord.maskName); } jsonSord = []; jsonSord.push('{ "summaryDesc": '); jsonSord.push(JSON.stringify(findResult.fulltextResultItems[i].summaryDesc + "")); jsonSord.push(', "relevance":'); jsonSord.push(JSON.stringify(findResult.fulltextResultItems[i].summaryDesc + "")); jsonSord.push(', "relevance":'); jsonSord.push(findResult.fulltextResultItems[i].relevance); jsonSord.push(', "sord":'); jsonSord.push(formatter.buildJson(findResult.fulltextResultItems[i].sord, me.docMasks[findResult.fulltextResultItems[i].sord.maskName])); jsonSord.push("}"); jsonData.push(jsonSord.join("")); } me.jsonData.push('"searchResults":'); me.jsonData.push("["); me.jsonData.push(jsonData.join(",")); me.jsonData.push("]"); } else { 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(formatter.buildJson(findResult.sords[i], me.docMasks[findResult.sords[i].maskName])); } me.jsonData.push('"sords":'); me.jsonData.push("["); me.jsonData.push(jsonData.join(",")); me.jsonData.push("]"); } me.jsonData.push(', "searchId": "'); me.jsonData.push(findResult.searchId); me.jsonData.push('", "moreResults": '); me.jsonData.push(findResult.moreResults); } else { logger.debug("skiped sord collection ... no 'searchResult'"); } logger.exit("collectSords"); }, buildFindInfo: function (findInfo) { var me = this; if (me.searchMode === "DIRECT") { me.buildFindDirect(findInfo); } else { me.buildFindByIndex(findInfo); } }, buildFindByIndex: function (findInfo) { var me = this, filter, objKey, i, objKeyFilters = []; // apply optional mask filter if (me.maskName) { if (!findInfo.findByIndex) { findInfo.findByIndex = new FindByIndex(); } findInfo.findByIndex.maskId = me.maskName; } // apply parent Id filter if (me.parentId) { findInfo.findChildren = new FindChildren(); findInfo.findChildren.parentId = me.parentId; findInfo.findChildren.endLevel = me.endLevel; } // 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; } }, buildFindDirect: function (findInfo) { var me = this, FIND_DIRECT = ixConnect.getCONST().FIND_DIRECT, query = "", i, filter, findDirect; if (me.parentId) { throw "parentIds can't be passed if using iSearch queries."; } // apply optional mask filter if (me.maskName) { query += " (" + FIND_DIRECT.FIELD_MASK_NAME + ":\"" + me.maskName + "\") "; findInfo.findByIndex.maskId = me.maskName; } // apply filter if (me.filter && me.filter.length > 0) { for (i = 0; i < me.filter.length; i++) { filter = me.filter[i]; query += " (" + FIND_DIRECT.FIELD_OBJ_KEY + filter.key + ":\"" + filter.val + "\") "; } } findDirect = new FindDirect(); if (!!me.query && query) { query = "(" + me.query + ") ( " + query + " )"; } else if (query) { query = "(*) ( " + query + " )"; } else if (!!me.query) { query = me.query; } findDirect.query = query; findDirect.searchInMemo = true; findDirect.searchInFulltext = true; findDirect.searchInIndex = true; findDirect.searchInSordName = true; findInfo.findDirect = findDirect; }, /** * @private * @return {Object} */ getSordFormatter: function () { var me = this; return sol.create(me.formatter, { config: { allObjKeys: me.allObjKeys || false, objKeys: me.objKeys, sordKeys: me.sordKeys, feedActions: me.feedActions || false, feedActionTypes: me.feedActionTypes || ["UserComment"], feedActionsMax: me.feedActionsMax || 500 } }); }, /** * @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; docMask = sol.common.SordUtils.getDocMask(docMaskName, me.ec.ci.language); docMaskData = { fields: {} }; docMask.lines.forEach(function (docMaskLine) { if (me.objKeyMap[docMaskLine.key]) { docMaskData.fields[docMaskLine.key] = { name: String(docMaskLine.name), type: me.docMaskLineTypes[docMaskLine.type] }; } }); 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.SordDataCollector * @method RF_sol_common_services_SordDataCollector_FindFirst * @static * @inheritdoc sol.common.ix.ServiceBase#RF_ServiceBaseName */ function RF_sol_common_services_SordDataCollector_FindFirst(ec, configAny) { var jsonDataCollector, config, ecLang, ixConnectLang, result; config = sol.common.ix.RfUtils.parseAndCheckParams(ec, arguments.callee.name, configAny), ecLang = String(ec.ci.language), ixConnectLang = String(ixConnect.loginResult.clientInfo.language); config.ec = ec; log.info("ec.ci.language=" + ecLang); log.info("ixConnect.loginResult.clientInfo.language=" + ixConnectLang); jsonDataCollector = sol.create("sol.common.ix.services.SordDataCollector", config); result = jsonDataCollector.execute(); return result; } /** * @member sol.common.ix.services.SordDataCollector * @method RF_sol_common_services_SordDataCollector_FindNext * @static * @inheritdoc sol.common.ix.ServiceBase#RF_ServiceBaseName */ function RF_sol_common_services_SordDataCollector_FindNext(ec, configAny) { var jsonDataCollector, config, ecLang, ixConnectLang, result; config = sol.common.ix.RfUtils.parseAndCheckParams(ec, arguments.callee.name, configAny, "searchId", "idx"), ecLang = String(ec.ci.language), ixConnectLang = String(ixConnect.loginResult.clientInfo.language); config.ec = ec; log.info("ec.ci.language=" + ecLang); log.info("ixConnect.loginResult.clientInfo.language=" + ixConnectLang); jsonDataCollector = sol.create("sol.common.ix.services.SordDataCollector", config); result = jsonDataCollector.execute(); return result; } /** * @member sol.common.ix.services.SordDataCollector * @method RF_sol_common_services_SordDataCollector_FindClose * @static * @inheritdoc sol.common.ix.ServiceBase#RF_ServiceBaseName */ function RF_sol_common_services_SordDataCollector_FindClose(ec, configAny) { var config; config = sol.common.ix.RfUtils.parseAndCheckParams(ec, arguments.callee.name, configAny, "searchId"); ixConnect.ix().findClose(config.searchId); return "{ \"success\": true }"; }