importPackage(Packages.de.elo.ix.client);
//@include lib_Class.js
//@include lib_sol.common.ObjectUtils.js
//@include lib_sol.common.RepoUtils.js
//@include lib_sol.common.StringUtils.js
//@include lib_sol.common.AclUtils.js
//@include lib_sol.common.ObjectFormatter.js
/**
* Retrieves sords using a search (findByIndex) or by predefined ids and formats the retrieved sords according to
* the output parameter. If fuzzy search (getContextTerms) is used, the output parameter is ignored since this
* search variant returns value groups just like an SQL GROUP BY query.
*
* This service is optimized by analyzing defined search terms and output options to retrieve the results in the
* fastest way possible. Additionally, this service caches a subset of analyzed parameters on demand to further
* improve execution time.
*
* ### Searching
*
* Two search variants are provided. findByIndex and getContextTerms
*
* #### Search Definition
*
* Both search variants use the same search definition. This makes it easier to switch between variants if needed.
* FindByIndex is used as a default.
*
* A search definition contains a `masks` property and a `search` property, which in turn contains additional search criteria.
*
* The masks property is an Array of Strings, where each string must define a mask to include in the search.
*
* masks: ["My Mask", "My Mask 2"]
*
* The search `property` can contain search criteria.
*
* search: [
* { key: "SOL_TYPE", value: "RECRUITING_CANDIDATE" },
* { key: "DEPARTMENTS", value: ["Sales", "Purchasing"] },
* { key: "ACTIVITYSTATUS", value: "A - *"},
* { key: "SOL_REFERENCE", value: "REF-0001¶REF-0002"}
* ]
*
* Hint: Sordprovider search values provides MultiIndex strings. If the string contains a pilcrow
* the value will be converted to ["REF-0001", "REF-0002"]
*
* You must also define an output definition. (see further below)
*
* To search for a date field, use the standard ELO syntax (YYYYMMDDHHMM or YYYYMMDD), but also define the property "type: date".
*
* "search": [
* { "key": "INVOICE_DATE", "value": "197310160000...202309300000", "type": "date" }, // range
* { "key": "RETRIEVAL_DATE", "value": "-0000-00-30...+0000-00-00", "type": "date" } // range using offset (-30days to today)
* ]
*
* If you need to search for an xDateIso or iDateIso, simply define XDATEISO or IDATEISO as the search-key.
*
* Date searches are only implemented for FindByIndex searches. Fuzzy/FindDirect searches will not work!
*
* If you need to search for a name, simply define name as the search-key
*
* "search": [
* { "key": "name", value: "shortDescription"}
* ]
*
* Attention: A search via the short name always leads to a full table scan on the database.
* The query is very slow with large databases.
*
*
* #### Fuzzy Search (getContextTerms)
*
* Fuzzy search can be used if the results need to be grouped.
* Fuzzy means, the result is not guaranteed to reflect the current state of the ELO sql database.
* Fuzzy search and therefore getContextTerms queries iSearch which uses another higher performance
* database containing possibly out of date values. (Re-indexing takes place about every minute)
*
* If you need 100% valid results you must not use fuzzy search. If speed matters most and you only need
* an estimated grouped result, you may use fuzzy search.
*
* Example for a fuzzy search grouped by field/property "a"
*
* results before grouping:
* [{ a: "test", b: "rest" }, { a: "now", b: "test" }, { "a: test", c: "what" }]
* results after grouping:
* {test: 2, now: 1}
*
* Note: when using fuzzy search, you only have access to the `results after grouping`.
*
* To activate fuzzy search, define
*
* options: {
* fuzzy: { groupBy: { type: "GRP", key: MY_GROUPING_FIELD } } // only GRP supported for now
* }
*
* You can limit the number of returned groups by defining
*
* options: {
* fuzzy: { groupBy: { type: "GRP", key: MY_GROUPING_FIELD }, maxGroups: 20 }
* }
*
* ##### Case Sensitivity
*
* To turn off case sensitivity, define `ignoreCase: true` in the search definition for the respective field:
*
* search: [
* { key: "SUBJECT", value: "Progress", "ignoreCase": true },
* { key: "DEPARTMENTS", value: ["Sales", "Purchasing"] }
* ]
*
* would return e.g.
*
* {Progress: 2, PROGRESS: 5, progreSs: 1}
*
* #### FindDirect search
*
* To use findDirect as the search mode (will additionally search in memos, fulltext,...), pass
*
* options: {
* findDirect: true
* }
*
* If you need to define a custom query term, pass
*
* options: {
* query: "your term"
* }
*
* If a query is passed, you don't have to pass `findDirect: true` also.
*
* #### Tokenization (findDirect only)
*
* To search in the tokenized elastic search field, define `tokenized: true` in the search definition for the respective field:
*
* search: [
* { key: "SUBJECT", value: "Progress", "tokenized": true },
* { key: "DEPARTMENTS", value: ["Sales", "Purchasing"] }
* ]
*
* #### Sanitize value
* By default search Values will be sanitized to remove possible escape characters (hyphen), blank values and double/triple whitespaces.
* To disable sanitation of these values you can disable it seperatly. Please be careful, without sanitation it will be easier to generate wrong search queries.
*
* E.g.
* search: [
* { key: "A_KEY", value: "A_VALUE-WITH_HYPHEN", sanitize: { escapeValues: false } },
* { key: "A_KEY", value: "A_VALUE-WITH_HYPHEN", sanitize: { blankValues: false } },
* { key: "A_KEY", value: "A_VALUE-WITH_HYPHEN", sanitize: { removeDupWhiteSpaces: false } }
* ]
*
*
* #### Paged Searching
*
* If you know there will be lots of results, it may make sense to use a paged search.
*
* To activate paging, define
*
* options: { paging: true }
*
* The service result will then contain an additional property `searchId`, which is a unique search id.
*
* {
* searchId: "(141DD-12QW-46GS-9928JJ)",
* moreResults: true,
* sords: [...]
* }
*
* If you want to fetch the next batch of results, call the service again by replacing the `paging: true` with `searchId` and the received id:
*
* {
* options: { searchId: "(141DD-12QW-46GS-9928JJ)" }
* }
*
* The last batch of results will neither contain a search id nor the moreResults property.
*
* ##### Controlling paging
*
* Define the following, to retrieve 10 search results starting from the 131st result.
*
* {
* options: { searchId: "(141DD-12QW-46GS-9928JJ)", startPagingFrom: 130, pageSize: 10 }
* }
*
* You can define any integer greater than -1 as startPagingFrom.
* When there are no more results, instead of throwing an exception, an empty result.sords array will be returned.
*
* ### Page sizes
*
* You can define how many results should be delivered per batch by defining a `pageSize`
*
* {
* options: { pageSize: 100 }
* }
*
* Page sizes greater than 1000 are not allowed and will automatically be reduced to 1000.
*
* To end a paged search/close a search, pass
*
* {
* options: { searchId: "(141DD-12QW-46GS-9928JJ)", endPaging: true }
* }
*
* This will return `{ sords: [] }`.
*
* If you try to fetch results for a searchId which has been closed, an exception will be thrown.
*
* ### Formatting
*
* Search results will be processed by a formatter. The formatter options must be provided in the `output` parameter.
*
* output: [
* { source: { type: "SORD", "key": "guid" }, target: { prop: "id" } },
* { source: { type: "MAP", "key": "MY_MAP_FIELD" }, target: { prop: "myprop" } }
* ]
*
* If 3 sords were found during search, these formatter options would result in
*
* {
* sords: [
* { id: "(345AD-DFS22-2FSF3-121WD)", myprop: "Testvalue" },
* { id: "(3573D-DF6522-2FAVF3-1SDD)", myprop: "Another value" }
* { id: "(3J5AD-DFQQ2-2FSFY-12G)", myprop: "Result 3" }
* ]
* }
*
* #### Wildcards for retrieving an unknown amount of MAP/BLOB fields having prefix x
*
* You can include MAP-fields in the output using a wildcard. This can be handy if you don't
* know how many MAP-fields with a specific prefix will exist in the sord.
*
* output: [
* { source: { type: "MAP", "key": "LOCALE_*" }, target: { prop: "*" } }
* ]
*
* Hint: `prop` will be ignored, you can define whatever you like. However, to make clear what you want to do, please define `"*"` or similar.
*
* This would output e.g.:
*
* {
* sords: [
* { LOCALE_APPLE: "sol.lang.apple", LOCALE_NEW: "sol.lang.new" },
* { LOCALE_APPLE: "sol.lang.apple", LOCALE_NEW: "sol.lang.new", LOCALE_TREE: "sol.lang.tree" },
* ]
* }
*
* #### Receive permissions on each result sord object
*
* If you want to checkout aclItems, `SordProvider` supports the concepts of converter. At the moment `SordProvider`
* supports two converters to convert `sord.aclItems`.
*
* Either you can return the accesscode of the sord object or you can define `aclItems` output definition
* to return permission object like in the example.
*
* When you define aclItems without an explicit converter, each time the converter for the access code is used
* automatically. It will be thrown an exception if the converter is unknown.
*
* Hint: converter prop of the output definition will only be used for sordKeys currently.
* Another types are currently not supported like objKeys, mapKeys, formBlobKeys and so on.
*
* output: [
* { source: { type: "SORD", "key": "guid" }, target: { prop: "id" } },
* { source: { type: "SORD", "key": "aclItems", converter: "ACCESS_RIGHTS_CONVERTER" }, target: { prop: "permission" } }
* ]
*
* The output will be:
*
* {
* sords: [
* { id: "(345AD-DFS22-2FSF3-121WD)", permission: { r: true, w: true, d: true, e: true, l: true, p: true} }
* ]
* }
*
* #### Format as TemplateSord
*
* Define the parameter
*
* {
* options: { formatAsTemplateSord: true }
* }
*
* When this parameter is defined, the `target.prop` name is ignored by default. A template sord based on the original fieldnames will be created.
*
* To use the name defined in `target.prop`, define
*
* {
* options: { formatAsTemplateSord: true, ignorePropertyNames: false }
* }
*
* #### Adding properties with custom values
*
* You may want to add a fixed property to every result. This is possible by only defining a target.
*
* output: [
* { target: { prop: "myspecialprop", value: "My Fixed Value" } }
* ]
*
* #### Format with ObjectFormatter
*
* You can use ObjectFormatters as defined in lib_sol.common.ObjectFormatter to format the output.
* To do so, define formatter and config within the `output` parameter, e.g.:
*
* output: {
* formatter: 'sol.common.ObjectFormatter.TemplateSord',
* config: {}
* }
*
* the defined option parameter regarding output formatting "formatAsTemplateSord" and "ignorePropertyNames" will be ignored.
*
* ### Skipping search and providing ids directly
*
* If you only want to use the formatting facilities of this service, you can omit the search definition and
* just provide objIds/guids directly. The service will automatically determine, whether you passed guids or objIds.
*
* ids: ["12345", "43241"]
*
* ObjIds and guids can't be mixed.
*
* ### Filtering results
*
* When you perform a normal search, results will already be filtered by the search criteria. However, you may also want to filter e.g. based on
* a search result's MAP field. This is made possible by the `filter` parameter.
*
* Also, if you pass guids or objIds instead of searching, you would always receive all of the formatted sords, no matter, which criteria they have.
* The same filter mechanism can be applied to this use-case.
*
* Only properties which are defined as a `target.prop` in the `output`-definition can be filtered.
*
* If a sord matches the filter, it will be kept. If it does not match the filter, it will be removed from the result-set.
*
* Just like the output-parameter, if using an optimized search, the parsed filter will be cached. This also means, that you can omit it
* on consecutive calls since it will be ignored anyways.
* (This can be done e.g. by adding it to the array parameter of the ixUtils.optimizedExecute function.)
*
* #### Applying a filter
*
* filter: [
* { prop: "soltype", value: "RECRUIT*_*" },
* { prop: "status", value: ["A -*", "B -*", "C -*"] }
* ]
*
* This example would only return sords having a soltype property value of "RECRUIT" + Any characters + "_" + Any characters
* while also having one of the status values beginning with "A -" or "B -" or "C -".
*
* #### Applying a filter in TemplateSord mode
*
* If `formatAsTemplateSord: true` is passed as an option, you need to define the whole property path to apply a filter.
*
* filter: [
* { prop: "objKeys.soltype", value: "RECRUIT*_*" },
* { prop: "mapKeys.status", value: ["A -*", "B -*", "C -*"] }
* ]
*
*
* #### Excluding a field only used for filtering from the sord-result
*
* If you include a field in the output-definition, just to enable its usage in the filter, you might not want to include
* the respective field in the end-result. Just add the `exclude:true` property to the desired definition.
*
* filter: [
* { prop: "soltype", value: "RECRUIT*_*", "exclude": true },
* { prop: "status", value: ["A -*", "B -*", "C -*"] }
* ]
*
* Hint: excluding a field will not work in TemplateSord mode.
*
* ### Options
*
* #### Returning sparse results
*
* Returns only properties which have a value. Empty strings are not a value.
*
* This can further reduce network bandwith if defined.
*
* options: { sparse: true }
*
* Hint: Caused by implementation details of the indexserver api, map and formblob values are not included
* in the result sord in the first place, even if `sparse` is undefined or false.
*
* #### Allowing searches without a mask
*
* For security reasons, the `search` must always contain a mask. (Read: Must not contain an empty string as mask)
*
* // Not allowed:
* 1. masks: []
* 2. masks: [""]
* 3. masks: ["test", ""]
*
* You may accidentally define the third variant, which would produce duplicate results.
*
* To allow the second variant or just omit the masks parameter completely, alltogether
*
* options: { allowEmptyMask: true }
*
* Tip: If a single mask is defined, the search will be fastest. If you define multiple masks or no masks, the formatter will have
* to lookup the mask for every sord.
*
* #### Limit Search Results
*
* You know there are 100000 results in the archive, but you only want 300.
*
* options: { maxResults: 300 }
*
* `maxResults` defaults to 5000.
*
* ### Optimization
*
* To further improve the service's performance, you can make it reuse the parsed formatter definitions.
*
* Attention: This should only be used, when it is guaranteed, that the output-parameters stay the same for
* every call of the service when using the same optimization id.
*
* Note: Optimization can not be used with fuzzy mode. (Since there is no output parameter to cache)
*
* To activate optimization, define
*
* {
* optimize: true
* }
*
* The service result will then contain an additional property `optimization`, which is a unique optimization cache Id.
*
* {
* optimization: 59921231,
* sords: [...]
* }
*
* When you call the service the next time, you can omit the output parameter and replace the `true` of `optimize` by the id.
*
* {
* optimize: 59921231
* }
*
* This will speed up the function call by some milliseconds. When searching 1000 results, this may not be a big factor,
* but when only fetching some ids, it will not make much of a difference if you handcode the whole process or just
* use this function.
*
* #### Implementing optimization in a calling class
*
* This is a recommendation on how to keep track of the optimization ids in your own class.
*
* Set up a class member
*
* _optimizations: {} // this will be your local cache for storing the optimization ids
*
* You can now call sol.common.IxUtils.optimizedExecute whenever you need it.
*
* process: function () {
* var me = this, candidates, apples;
* candidates = sol.common.IxUtils.optimizedExecute("RF_sol_common_service_SordProvider", me.myConfigForSearchingCandidates, me._optimizations, "candids", ["output"]);
* apples = sol.common.IxUtils.optimizedExecute("RF_sol_common_service_SordProvider", me.myConfigForSearchingApples, me._optimizations, "appls", ["output"]);
* return [candidates, apples];
* }
*
* The optimizedExecute function can be used with any function implementing the same behaviour as SordProvider (taking
* an `optimize` parameter {Boolean|String}, returning an `optimization` {String})
*
* The first parameter takes the RF to execute, the second parameter are the arguments passed to the RF (usually an Object).
*
* The third parameter defines that we would like to use _optimizations as our optimization id cache.
*
* The fourth parameter enables calling the function in an optimized way for different configs.
*
* The fifth parameter defines which config-properties should not be transfered to the RF when an optimization is available. E.g. SordProvider does not need the output
* parameter because it caches the instructions generated from this parameter.
*
* You can make up any name for an optimization. However, it should be unique in your class
* since the name decides on which optimization id will be passed to the RF.
*
* ### Examples
* #### Give an objId, get a guid
*
* Call
* { ids: ["12345"], output: [{ source: { type: "SORD", key: "guid" } }] }
* Result
* { sords: ["(345AD-DFS22-2FSF3-121WD)"] }
*
* #### Search without mask, return a single result
*
* Call
* {
* masks: [""],
* search: [
* { key: "SOL_TYPE", value: ["RECRUITING_REQUISITION", "RECRUITING_POOL"] }
* { key: "RECRUITING_REQUISITION_NO", value: "0001" }
* ],
* output: [
* { source: { type: "SORD" , key: "guid" }, target: { prop: "guid" } },
* { source: { type: "GRP" , key: "RECRUITING_REQUISITION_NAME" }, target: { prop: "name" } }
* ],
* options: {
* allowEmptyMask: true,
* maxResults: 1
* }
* }
* Result
* { sords: [{ guid: "(2A15C78E-866E-D9FE-0A44-832AFFAF85EE)", name: "IT Engineer" }] }
*
* #### Search with multiple masks and paging, return 80 results in batches of 50
*
* There is one "Recruiting Requisition" in the archive and more than 79 "Recruiting Candidate"s
*
* Call 1
* {
* masks: ["Recruiting Requisition", "Recruiting Candidate"],
* search: [{ key: "RECRUITING_REQUISITION_NO", value: "0001" }],
* output: [
* { source: { type: "SORD" , key: "guid" }, target: { prop: "guid" } },
* { source: { type: "SORD" , key: "name" }, target: { prop: "name" } }
* ],
* options: {
* paging: true,
* pageSize: 50
* }
* }
* Result 1
* {
* sords: [
* { guid: "(BEDCE611-7B78-9C48-E9D9-8DFE7A2ABAB3)", name: "Senior IT Engineer (0001)" },
* { guid: "(5C9A204C-D16C-4EC8-C5AF-5933ECA37120)", name: "Fesh, Dieter (00234)" },
* // 48 more ...
* ],
* searchId: "(5C041998-C430-11D8-04A8-CA1EC9CC2502)"
* }
*
* Call 2
* {
* options: {
* searchId: "(5C041998-C430-11D8-04A8-CA1EC9CC2502)",
* startPagingFrom: 49
* pageSize: 50
* }
* }
* Result 2
* {
* sords: [
* { guid: "(B57CBBE6-81D8-75CC-836D-26717DDCF7FC)", name: "Monkfish, Thelonious (0005)" },
* { guid: "(1A6E094C-A49E-17A9-19B4-FB56A8FD4E04)", name: "Nex, Carl (0001)" }
* // 28 more ...
* ]
* }
*
* @elojc
* @eloas
* @eloix
*
* @author ESt, ELO Digital Office GmbH
*
* @requires sol.common.ObjectUtils
* @requires sol.common.StringUtils
* @requires sol.common.ObjectFormatter
* @requires sol.common.AclUtils
*
*/
sol.define("sol.common.SordProvider", {
/**
* @cfg {String} id (optional)
* A single objId or guid. The sord is fetched, formatted and returned.
*/
/**
* @cfg {String[]} ids (optional)
* An Array of objIds or guids. All sords are fetched, formatted and returned.
*/
/**
* @cfg {String[]} masks (optional)
* Masks which will be included in the search (`OR` concatenation). Define an empty string as array member to include all masks.
* If all masks are included, the `option.allowEmptyMask` must be true.
*/
/**
* @cfg {Object[]} search (optional)
* @cfg {Object} search.Object
* @cfg {String} search.Object.key name of field
* @cfg {String|String[]} search.Object.value search value. If an array is defined, entries will be concatenated with `OR`
*/
/**
* @cfg {Object[]} output (optional)
* @cfg {Object} output.Object
* @cfg {Object} output.Object.source (optional)
* @cfg {String} output.Object.source.key name of source field
* @cfg {String|"SORD"|"GRP"|"MAP"|"FORMBLOB"} output.Object.source.type type of field
* @cfg {Object} output.Object.target
* @cfg {String} output.Object.target.prop name of target property
* @cfg {String} output.Object.target.value (optional) value for property (useful if no `source` defined)
*/
/**
* @cfg {Object[]} filter (optional)
* @cfg {Object} filter.Object
* @cfg {String} filter.Object.prop name of the property to read. (property must be defined as a `target.prop` in the `output` parameter)
* @cfg {String|String[]} filter.Object.value filter value. If an array is defined, entries will be concatenated with `bitwise OR`. The filter is to be understood as working like the array.filter method.
*/
/**
* @cfg {Object} options (optional)
* @cfg {Boolean} [options.allowEmptyMask = false] allow `masks` array to have an empty string as member
* @cfg {Number} [options.maxResults = 5000] maximum results for the search. if paging is active, a maximum of 1000 results is returned per paged search. Paged searches ignore this parameter.
* @cfg {Boolean|String} [options.paging = false] (optional) if true, searches use paging and return a paging id. If the `paging` value of a result is passed, the paged search is continued.
* @cfg {Number} [options.pageSize = 1000] (optional) if paging is active. this defines how many results will be returned per search
* @cfg {Object} options.fuzzy (optional) use getContextTerms instead of findByIndex for searching
* @cfg {Object} options.fuzzy.groupBy
* @cfg {String|"GRP"} options.fuzzy.groupBy.type only "GRP" supported so far.
* @cfg {String} options.fuzzy.groupBy.key name of field to use for grouping
* @cfg {Number} [options.fuzzy.maxGroups = 25] (optional) maximum groups to be returned
*/
/**
* @cfg {Boolean|Number} [optimize = false] (optional) if true, caches analyzed parameters. If the `optimization` value of a result is passed, it will use the cached parameters instead of analyzing again.
*/
pilcrow: String.fromCharCode(182),
/**
* @property {Number} [defaultMaxResults = 5000] Limits how many results will be returned by a search. Overwritten by `options.maxResults` if defined by user
*/
defaultMaxResults: 5000,
/**
* @property {Number} [defaultMaxGroups = 25] Limits how many groups will be returned by a fuzzy search. Overwritten by `options.fuzzy.maxGroups` if defined by user
*/
defaultMaxGroups: 25,
/**
* @property {String} [wildCard = "*"] Character which will be used as the wildcard character in fuzzy searches.
*/
wildCard: "*",
/**
* @property {Number} [pageSizeMax = 1000] Hard limit. How many results are returned per findFirstSords/findNextSords. `pageSize` gets sanitized to this value if it is defined and greater
*/
pageSizeMax: 1000,
/**
* @private
* mask line mappings for faster access to GRP-field values
*/
maskObjKeyCache: {},
/**
* @private
* optimized `output` definitions
*/
manualOptimizationCache: [],
/**
* @private
* current idx and remaining desired results for each search
*/
searchCache: {},
/**
* @private
* is used as property name, if only ids are returned but direct values are set and no target.prop was defined.
*/
fallbackIdProp: "id",
/**
* @private
* Use specific SordC for these values.
*/
BASICSORDKEYS: ["ID", "GUID", "NAME", "DESC", "MASK", "XDATEISO", "IDATEISO", "ACLITEMS"],
/**
* @private
* mb map
*/
MBS: {
ID: SordC.mbOnlyId.getBset(),
GUID: SordC.mbOnlyGuid.getBset(),
NAME: SordC.mbName,
DESC: SordC.mbDesc,
MASK: SordC.mbMask,
XDATEISO: SordC.mbXDateIso,
IDATEISO: SordC.mbIDateIso,
MIN: SordC.mbMin.getBset(),
GRP: SordC.mbObjKeys,
ACLITEMS: SordC.mbAclItems
},
/**
* @private
*/
CONVERTERS: {
/**
* Retrieve the acces code of the sord object by the aclItems
*/
ACCESS_CODE_CONVERTER: function (sord) {
return String(sol.common.AclUtils.getAccessCode(sord));
},
/**
* Retrieve the rights object {r: true, w: true, ...} of the sord object
*/
ACCESS_RIGHTS_CONVERTER: function (sord) {
return sol.common.AclUtils.getAccessRights(sord);
}
},
DEFAULT_CONVERTERS: {
aclItems: "ACCESS_CODE_CONVERTER"
},
/**
* @private
* used to decide on which property should be read during formatting
*/
SORDACCESSORS: {
SORD: "sordKeys",
GRP: "objKeys",
MAP: "mapKeys",
FORMBLOB: "formBlobs"
},
/**
* @private
* these values will be escaped in findDirect search queries
*/
ESCAPE_VALUES: [
"(-)"
],
/**
* @private
* these values will be removed from findDirect search queries
*/
BLANK_VALUES: [
"\\[", "\\]", "\\{", "\\}", "=", "\\|", "\\(", "\\)", "\\^", "\\\"", "~", ":", "\\\\", "/", "<", ">", "\\+", "&", "!", "\\?"
],
/**
* @private
* helpers for collectDataFromId
*/
/**
* @returns {de.elo.ix.Client.Sord[]} sords by objIds
*/
getSords: function (objIds, sordC) {
var me = this;
return (objIds || [])
.map(function (objId) {
return me.getSord(objId, sordC || SordC.mbAllIndex);
});
},
/**
* @returns {de.elo.ix.Client.Sord} sord by objId
*/
getSord: function (objId, srdC) {
try {
return ixConnect.ix().checkoutSord(objId, srdC, LockC.NO);
} catch (e) {
throw "SordProvider: could not checkout sord with objId/guid `" + objId + "`";
}
},
var me = this, field, i = fields.length, targetProp, ignorePropNames = me.ipn, converterFct;
while (i--) {
field = fields[i];
targetProp = ignorePropNames ? field.key : (field.prop || field.key);
converterFct = me.getOutputConverterFunction(field);
if (converterFct) {
// use selected converter function to convert sord prop to
// specific result (e.g. use ACCESSRIGHTSCONVERTER) to format
// aclItems to {r: true, w: true, ...} formatted object
result[targetProp] = converterFct(sord);
} else {
result[targetProp] = String(sord[field.key] || "");
}
}
},
getOutputConverterFunction: function (field) {
var me = this, converterFctKey, converterFct;
if (!sol.common.StringUtils.isBlank(field.converter)) {
converterFct = me.CONVERTERS[field.converter.toUpperCase()];
if (!converterFct) {
throw Error("SordProvider: converter " + field.converter + " does not exist. Only " + Object.keys(me.CONVERTERS) + " are allowed");
}
} else {
converterFctKey = me.DEFAULT_CONVERTERS[field.key];
converterFct = me.CONVERTERS[converterFctKey];
}
return converterFct;
},
var me = this, i = fieldMapping.length, field, fieldNo, d, dLen, str, values, pilcrow = me.pilcrow,
key, targetProp, ignorePropNames = me.ipn,
maskObjKeys = (me.maskObjKeyCache[mask || +(sord.mask)])
|| me.generateObjKeyLineMapping(mask || +(sord.mask));
while (i--) {
key = (field = fieldMapping[i]).key;
targetProp = ignorePropNames ? key : (field.prop || key);
if ((fieldNo = maskObjKeys[key]) !== undefined) {
if ((dLen = (values = sord.objKeys[fieldNo].data).length) > 1) {
for ((str = "", d = 0); d < dLen; d++) {
str += (values[d] || "");
(d + 1 < dLen) && (str += pilcrow);
}
result[targetProp] = str;
} else {
result[targetProp] = String(values[0] || "");
}
} else {
result[targetProp] = "";
}
}
},
return Array.prototype.slice.call((ixConnect.ix().checkoutMap(mapDomain, sord.id, fields, LockC.NO)).items);
},
var me = this, items = me.mapExtractor(sord, MapDomainC.DOMAIN_SORD, accessors.mapKeys), i = items.length, item, targetProp, ignorePropNames = me.ipn;
while (i--) {
item = items[i];
targetProp = ignorePropNames ? item.key : (fieldMapping[item.key] || item.key);
result[targetProp] = String(item.value);
}
},
var me = this,
items = me.mapExtractor(sord, "formdata", accessors.formBlobs),
i = items.length,
item, targetProp, ignorePropNames = me.ipn, stream;
while (i--) {
stream = (item = items[i]).getBlobValue().getStream();
targetProp = ignorePropNames ? item.key : (fieldMapping[item.key] || item.key);
result[targetProp] = String(Packages.org.apache.commons.io.IOUtils.toString(stream, java.nio.charset.StandardCharsets.UTF_8));
stream.close();
}
},
formatSord: function (sord, formatterConfig, configKeys, accessors, mask) {
var me = this, result = {}, i = configKeys.length, key;
while (i--) {
me[(key = configKeys[i]) + "Extractor"](result, sord, formatterConfig[key], accessors, mask);
}
return result;
},
formatSordAsTemplateSord: function (sord, formatterConfig, configKeys, accessors, mask) {
var me = this, result = {}, i = configKeys.length, key;
while (i--) {
me[(key = configKeys[i]) + "Extractor"](
(key === "sordKeys" ? result : (result[key] = {})),
sord,
formatterConfig[key],
accessors,
mask
);
}
return result;
},
collectDataFromId: function (id, instructions, mask) {
var me = this, sord = {};
if (instructions.sordZ) {
sord = me.getSord(id, instructions.sordZ);
} else if (instructions.formatterRequired) {
sord.id = id;
}
if (instructions.formatterRequired) {
return me.getFormatter()(
sord,
instructions.converterConfig,
instructions.configKeys,
instructions.mapAccessors,
mask
);
}
return sord;
},
getFormatter: function () {
var me = this;
return me.formatAsTemplateSord
? me.formatSordAsTemplateSord.bind(me)
: me.formatSord.bind(me);
},
setUnchangedIdsAsResult: function (resultArr, ids) {
var me = this, i, len = ids.length;
me.logger.debug("Mode: Input is output");
for (i = 0; i < len; i++) {
resultArr.push(String(ids[i]));
}
me.logger.debug(ids.length + " sords processed");
},
reinitializeFormatterSettingsIfRequired: function (instructions) {
if (!instructions.sordZ && !instructions.converterconfig && !instructions.formatterRequired) {
instructions.formatterRequired = true;
instructions.sordZ = instructions.idName === "guid" ? SordC.mbOnlyGuid : SordC.mbOnlyId;
instructions.converterConfig = { sordKeys: [{ key: instructions.idName, prop: "__id" }] };
instructions.configKeys = ["sordKeys"];
}
},
getIdsOfIds: function (resultArr, ids, instructions) {
var me = this, i, len = ids.length;
me.logger.debug("Mode: Get objIds of guids or guids of objIds");
me.reinitializeFormatterSettingsIfRequired(instructions);
for (i = 0; i < len; i++) {
resultArr.push(me.collectDataFromId(ids[i], instructions)["__id"]);
}
me.logger.debug(ids.length + " sords formatted");
},
defaultDataCollection: function (resultArr, ids, instructions, mask) {
var me = this;
me.logger.debug("Mode: Default formatter");
if (instructions.objectFormatter) {
me.getFormattedDataCollectionFromObjectFormatter(ids, instructions)
.map(me.pushToResults.bind(me, resultArr));
} else {
(ids || [])
.map(function (id) {
return me.collectDataFromId(id, instructions, mask);
})
.map(me.pushToResults.bind(me, resultArr));
}
me.logger.debug(ids.length + " sords formatted");
},
getFormattedDataCollectionFromObjectFormatter: function (ids, instructions) {
var me = this;
return (sol.common.ObjectFormatter.format(
me.getObjectFormatterInput(ids, instructions)
) || {}).sords || [];
},
pushToResults: function (resultArr, formattedSord) {
resultArr.push(formattedSord);
},
getObjectFormatterInput: function (ids, instructions) {
var me = this;
return {
sords: {
formatter: instructions.objectFormatter.formatter,
data: me.getSords(ids, instructions.sordZ),
config: instructions.objectFormatter.config
}
};
},
generateObjKeyLineMapping: function (maskName) {
var me = this, maskLines, result = {}, i, line, DocMaskZ = new Packages.de.elo.ix.client.DocMaskZ();
me.logger.debug("Preparing cache for mask", maskName);
DocMaskZ.add(DocMaskC.mbLines);
i = (maskLines = Array.prototype.slice.call(ixConnect.ix().checkoutDocMask(maskName, DocMaskZ, LockC.NO).lines)).length;
while (i--) {
result[(line = maskLines[i]).key] = +(line.id);
}
me.maskObjKeyCache[maskName] = result;
return result;
},
prepareMask: function (mask) {
var me = this;
me.maskObjKeyCache[mask] || me.generateObjKeyLineMapping(mask);
},
addDataCollectedFromIdContainer: function (idContainer, instructions, allCollectedData, ofMask) {
var me = this,
mask,
idsOfMask = idContainer[ofMask],
idIsObjId = +(idsOfMask[0]) === +(idsOfMask[0]),
requiredIdIsAvailableId = (instructions.idName === "id" && idIsObjId) || (instructions.idName === "guid" && (!idsOfMask[0].indexOf("(")));
if (me.shouldUseDefaultDataCollection(instructions)) {
me.logger.debug("Collecting data for all ids of mask", mask);
if (instructions.objKeysRequired && (mask = (ofMask === "__nomask" ? undefined : ofMask))) {
me.prepareMask(mask);
}
me.defaultDataCollection(allCollectedData, idsOfMask, instructions, mask);
} else {
if (instructions.anIdRequired) {
instructions.onlyIdsInCollection = true; // this value persists for whole execution
if (requiredIdIsAvailableId) {
me.setUnchangedIdsAsResult(allCollectedData, idsOfMask);
} else {
me.getIdsOfIds(allCollectedData, idsOfMask, instructions);
}
}
}
return allCollectedData;
},
shouldUseDefaultDataCollection: function (instructions) {
return instructions.objectFormatter
|| (!instructions.onlyIdsInCollection
&& instructions.converterConfig
&& instructions.formatterRequired);
},
processIdContainer: function (idContainer, instructions) {
var me = this;
return Object.keys(idContainer || {})
.reduce(me.addDataCollectedFromIdContainer.bind(me, idContainer, instructions), []);
},
/**
* @private
* helpers for search
*/
createObjKey: function (name, value) {
var key = new ObjKey();
key.name = name;
key.data = [value];
return key;
},
parseSearchValue: function (value) {
var me = this;
value = sol.common.ObjectUtils.isString(value)
? me.convertMultiIndex(value)
: value;
// multiIndexValue could be a simple string here as well
return !Array.isArray(value)
? value
: value.reduce(function (acc, val, i) {
return me.addOR(acc + '"' + val + '"', i, value.length);
}, "");
},
convertMultiIndex: function (value) {
var me = this,
values = value
.split(me.pilcrow)
.filter(function (str) {
// filter out each broken multiIndex value like ¶¶Test
return str.trim();
});
// return simple string when it is a single value because it is faster
return values.length > 1 ? values : value;
},
var me = this, opts = {}, objKeys = [];
if (!Array.isArray(fields)) {
throw "SordProvider: searchfields must be an array of objects!";
}
fields.forEach(function (field) {
if ((typeof field !== "object") || (typeof field.key !== "string" && field.key) || ((typeof field.value !== "string") && (!Array.isArray(field.value)))) {
throw "SordProvider: searchfield is no object, or key or value not suited for searching";
}
if (Array.isArray(field.value) && !field.value.length) {
throw "SordProvider: " + field.key + ": it is not allowed to use empty arrays as a search value.";
}
if (field.key === "XDATEISO") {
opts.xDateIso = field.value;
} else if (field.key === "IDATEISO") {
opts.iDateIso = field.value;
} else if (field.key === "name") {
opts.name = field.value;
} else {
objKeys.push(me.createObjKey(field.key, me.parseSearchValue(field.value)));
}
});
if (!(objKeys.length || opts.xDateIso || opts.iDateIso || opts.name)) {
throw "SordProvider: no search criteria defined!";
}
objKeys.length && (opts.objKeys = objKeys);
return opts;
},
buildFindByIndex: function (masks, searchfields) {
var me = this, findByIndex = new FindByIndex(), opts = me.extractSearchOpts(searchfields);
opts.objKeys && (findByIndex.objKeys = opts.objKeys);
opts.xDateIso && (findByIndex.XDateIso = opts.xDateIso);
opts.iDateIso && (findByIndex.IDateIso = opts.iDateIso);
opts.name && (findByIndex.name = opts.name);
findByIndex.maskIds = masks;
return findByIndex;
},
buildFindInfoForFindByIndex: function (masks, searchfields) {
var me = this, findInfo = new FindInfo();
me.logger.debug("Building findInfo for findByIndex");
findInfo.findByIndex = me.buildFindByIndex(masks, searchfields);
return findInfo;
},
buildFindInfoForFindDirect: function (masks, searchCriteria) {
var me = this, findInfo = new FindInfo(), queryOpts;
me.logger.debug("Building findInfo for search via mask(s) using findDirect", masks);
sol.common.ObjectUtils.type(me.query, "string")
&& (queryOpts = { customQuery: String(me.query) });
findInfo.findDirect = me.buildFindDirect(masks, searchCriteria, queryOpts);
return findInfo;
},
getFindInfoBuilder: function () {
var me = this;
return (me.findDirect ? me.buildFindInfoForFindDirect : me.buildFindInfoForFindByIndex).bind(me);
},
/**
* @private
* helpers for finalizeCollector
*/
generateMbsFromOptions: function (mbs) {
var me = this;
return mbs.map(function (mb) {
return me.MBS[mb];
});
},
createSordZ: function (mbs) {
return (mbs.length || undefined)
&& mbs.reduce(function (sordZ, mb) {
sordZ.add(mb);
return sordZ;
}, new SordZ());
},
addToOptimizationCache: function (cache, instructions) {
var me = this;
me.logger.debug("Storing instructions in optimization cache");
return me[cache].push(instructions);
},
getFromOptimizationCache: function (cache, id) {
var me = this;
try {
return me[cache][id - 1];
} catch (e) {
throw "SordProvider: id `" + id + "` not found in optimization cache.";
}
},
getCachedInstructions: function (cacheName, optimization) {
var me = this;
return me.getFromOptimizationCache(cacheName + "OptimizationCache", optimization);
},
addInstructionsToCache: function (cacheName, instructions) {
var me = this;
return me.addToOptimizationCache(cacheName + "OptimizationCache", instructions);
},
/**
* @private
* helpers for parseOutputDefinition
*/
addMb: function (mbs, type, key) {
var me = this, uKey = key.toUpperCase(), mb = (
((type === "SORD") && (me.contains(me.BASICSORDKEYS, uKey) ? uKey : "MIN"))
|| ((type === "GRP") && type)
|| ""
);
return mb && !me.contains(mbs, mb) && mbs.push(mb);
},
addConverterKey: function (converterConfig, type, key, prop, converter) {
var formatterTarget = this.SORDACCESSORS[type];
if (!formatterTarget) {
throw "SordProvider: type " + type + " not supported";
}
(type === "SORD" || type === "GRP")
? converterConfig[formatterTarget].push({ key: key, prop: prop, converter: converter })
: converterConfig[formatterTarget][key] = prop; // map/blob definitions stored in objects!
},
cleanConfig: function (config) {
return Object.keys(config).reduce(function (cleanedConfig, key) {
if (Object.keys(config[key]).length) {
cleanedConfig[key] = config[key];
}
return cleanedConfig;
}, {});
},
formatterIsRequired: function (instructions) {
var onlyOneMbAndItsNotForAnId = (instructions.mbs.length === 1 && !instructions.anIdRequired),
multipleMbsRequired = instructions.mbs.length > 1;
return !!(
onlyOneMbAndItsNotForAnId // e.g. `DESC`, when only sord.desc is read
|| multipleMbsRequired
|| instructions.converterConfig.objKeys
|| instructions.converterConfig.mapKeys
|| instructions.converterConfig.formBlobs
);
},
calcFlags: function (instructions) {
var me = this, idRequired, guidRequired;
me.logger.debug("Calculating flags for generated instructions.");
idRequired = me.contains(instructions.mbs, "ID") && "id";
guidRequired = me.contains(instructions.mbs, "GUID") && "guid";
(instructions.anIdRequired = !!(idRequired || guidRequired))
&& me.logger.debug("Output will contain an objId or guid");
if (instructions.objKeysRequired = me.contains(instructions.mbs, "GRP")) {
me.logger.debug("Output will contain GRP-fields");
((!me.masks) || me.contains(me.masks, "__nomask"))
&& me.addMb(instructions.mbs, "SORD", "mask") // mask will be read from every sord
&& me.logger.debug("Since no mask was defined, `mask` has been added to SordZ of sord formatter");
}
instructions.formatterRequired = me.formatterIsRequired(instructions);
me.logger.debug((instructions.formatterRequired ? "" : "No ") + "sord formatter will be used.");
// determines which property will be read from sord when input>output is id>guid or guid>id
if (instructions.formatterRequired) {
(instructions.converterConfig.mapKeys || instructions.converterConfig.formBlobs)
&& me.addMb(instructions.mbs, "SORD", "id");
} else {
(instructions.idName = idRequired || guidRequired || "id")
&& me.logger.debug(["Using `{0}` as id property.", instructions.idName]);
}
},
optimizeObjectAccessors: function (instructions) {
var me = this;
me.logger.debug("Optimizing sord object accessors");
instructions.configKeys = Object.keys(instructions.converterConfig); // performance opt. for converterConfig
instructions.mapAccessors = me.cleanConfig({ // performance opt. for keys in configKeys
mapKeys: Object.keys(instructions.converterConfig.mapKeys || {}),
formBlobs: Object.keys(instructions.converterConfig.formBlobs || {})
});
instructions.dvKeys = Object.keys(instructions.directValues); // performance opt. for directValues
},
assignFallbackIdProp: function (instructions, lonelyDef) {
var me = this;
function definesIdOrGuid(source) {
return source.type === "SORD" && (source.key === "guid" || source.key === "id");
}
definesIdOrGuid(lonelyDef.source)
&& (instructions.fallbackIdProp = ((lonelyDef.target || {}).prop || me.fallbackIdProp));
},
addAsSourceTargetInstruction: function (instructions, source, target) {
var me = this;
me.logger.debug("Preparing sord formatter instructions for entry.");
me.addMb(instructions.mbs, source.type, source.key);
me.addConverterKey(instructions.converterConfig, source.type, source.key, target.prop, source.converter);
target && target.prop && instructions.targetProps.push(target.prop);
},
addAsDirectValueInstruction: function (instanceScope, instructions, target) {
var me = this;
me.logger.debug("Instruction is a direct value.");
instanceScope._directValuesDefined = true;
instructions.directValues[target.prop] = target.value;
instructions.targetProps.push(target.prop);
},
isValidSourceDefinition: function (sDef) {
return typeof sDef === "object" && typeof sDef.type === "string" && typeof sDef.key === "string";
},
definesDirectValue: function (isValidTarget, target) {
return isValidTarget && typeof target.prop === "string" && typeof target.value === "string";
},
addOutputInstruction: function (multipleOptsDefined, instructions, outputInstruction, i) {
var me = this,
source = outputInstruction.source, target = outputInstruction.target || {},
validTargetDefinition = !!(target && target.prop);
me.logger.debug("Processing output instruction " + (i + 1) + ":", outputInstruction);
if (me.isValidSourceDefinition(source)) {
if (multipleOptsDefined && !validTargetDefinition) {
throw "SordProvider: Invalid instruction: each instruction must have a `target.prop`";
}
me.addAsSourceTargetInstruction(instructions, source, target);
} else if (me.definesDirectValue(validTargetDefinition, target)) {
me.addAsDirectValueInstruction(me, instructions, target);
} else {
throw "SordProvider: Invalid instruction: each instruction must have (`source.type`, `source.key` and `target.prop`) or (`target.prop` and `target.value`)";
}
return instructions;
},
parseOutputDefinition: function (outputDef) {
var me = this,
multipleOptsDefined = (outputDef.length > 1),
instructions = {
mbs: [], // used for generating a sordZ for CheckoutSord
// defines, which values will be retrieved from the checked out sord
targetProps: [], // used when sparse option is set
converterConfig: { sordKeys: [], objKeys: [], mapKeys: {}, formBlobs: {} },
directValues: {}
};
me.logger.debug("Parsing output definition.", outputDef);
outputDef.reduce(me.addOutputInstruction.bind(me, multipleOptsDefined), instructions);
!multipleOptsDefined
&& me._directValuesDefined // defined in addOutputInstruction
&& me.assignFallbackIdProp(instructions, outputDef[0]);
instructions.converterConfig = me.cleanConfig(instructions.converterConfig);
me.calcFlags(instructions); // precalculate flags to save time in loops later on
me.optimizeObjectAccessors(instructions); // cache accessors (Object.keys()) in instructions
return instructions;
},
finalizeCollector: function (instructions) {
var me = this, mbs,
onlyOneMbAndItsForAnId = (instructions.mbs.length === 1 && instructions.anIdRequired);
// only generate a sordZ if more than the ID is to be included in the output
if (onlyOneMbAndItsForAnId && (instructions.converterConfig && (!instructions.converterConfig.mapKeys && !instructions.converterConfig.formBlobs))) {
me.logger.debug("No sord formatter will be used");
instructions.converterConfig = undefined;
instructions.sordZ = undefined;
instructions.formatterRequired = false;
} else {
// update instruction.mbs array here
mbs = me.generateMbsFromOptions(instructions.mbs);
instructions.sordZ = me.createSordZ(mbs);
}
},
idsOf: function (findResult) {
return (findResult && findResult.ids && Array.prototype.slice.call(findResult.ids)) || [];
},
impureConcat: function (target, source) {
for (var i = 0, len = source.length; i < len; i++) {
target.push(source[i]);
}
},
updateSearchCache: function (findResult, desiredResults) {
var me = this,
cache = (me.searchCache[findResult.searchId] || (me.searchCache[findResult.searchId] = {})),
results = (findResult.ids && findResult.ids.length) || 0;
me.logger.debug("Updating search cache");
cache.idx = cache.idx ? cache.idx + results : results;
cache.desiredResults = cache.desiredResults ? (cache.desiredResults - results) : (desiredResults - results);
},
getFromSearchCache: function (store, searchId) {
var me = this, cache = me.searchCache[searchId] || {};
me.logger.debug("Testing cache for search information");
store.idx = cache.idx;
store.desiredResults = cache.desiredResults;
},
closeFind: function (searchId) {
var me = this;
me.searchCache[searchId] = undefined;
ixConnect.ix().findClose(searchId);
},
maintainSearchInfo: function (searchInfo, findResult, maxResults) {
var me = this;
if (findResult.moreResults) {
me.updateSearchCache(findResult, maxResults);
me.getFromSearchCache(searchInfo, findResult.searchId);
findResult.moreResults = searchInfo.desiredResults > 0 ? findResult.moreResults : false;
me.logger.debug(
["Found `{1}` results so far, desired remaining results `{1}` "],
searchInfo.idx, searchInfo.desiredResults
);
}
},
prepareSearchInfo: function (searchInfo, searchId, maxResults, pageSize) {
var me = this, desiredResults;
me.getFromSearchCache(searchInfo, searchId);
pageSize || (pageSize = me.pageSizeMax);
desiredResults = searchInfo.desiredResults || maxResults;
searchInfo.maxSords = (desiredResults <= pageSize ? desiredResults : pageSize); // limit to pagesize
me.logger.debug([
"Find by searchId `{0}`, desired result count `{1}`, results so far `{2}`, max results per search `{3}` ",
(searchId || "N/A (new search)"), searchInfo.desiredResults, searchInfo.idx, searchInfo.maxSords
]);
},
find: function (findInfo, maxResults, pageSize, sordZ, searchId) {
var me = this, searchInfo = {}, findResult;
me.prepareSearchInfo(searchInfo, searchId, maxResults, pageSize);
if (me.manualPagingFrom === undefined || me.paging === true) {
findResult = (searchInfo.idx === undefined)
? ixConnect.ix().findFirstSords(findInfo, searchInfo.maxSords, sordZ)
: ixConnect.ix().findNextSords(searchId, searchInfo.idx, searchInfo.maxSords, sordZ);
me.maintainSearchInfo(searchInfo, findResult, maxResults);
me.moreResults = !!findResult.moreResults;
findResult.moreResults
? me.logger.debug("More results available. Id: " + findResult.searchId)
: me.closeFind(String(findResult.searchId));
} else {
findResult = ixConnect.ix().findNextSords(searchId, me.manualPagingFrom, searchInfo.maxSords, sordZ);
findResult.moreResults && (me.moreResults = true);
}
return findResult;
},
processResult: function (findResult, ids) {
var result = { ids: ids };
if (findResult.moreResults) {
result.paging = String(findResult.searchId);
}
return result;
},
findAll: function (findInfo, maxResults, sordZ) {
var me = this, ids = [],
findResult = { moreResults: true };
while (findResult.moreResults) {
findResult = me.find(findInfo, maxResults, undefined, sordZ, findResult.searchId);
me.impureConcat(ids, me.idsOf(findResult));
}
me.logger.debug(["Found {0} results", ids.length]);
return me.processResult(findResult, ids);
},
pageFind: function (findInfo, maxResults, pageSize, sordZ, searchId) {
var me = this, findResult;
me.logger.debug("Executing find using paging.");
me.logger.debug((searchId ? "Continuing" : "Starting new") + " search");
findResult = me.find(findInfo, maxResults, pageSize, sordZ, searchId);
return me.processResult(findResult, me.idsOf(findResult));
},
findIds: function (masks, searchFields, options) {
var me = this, paging = options.paging,
maxResults = (options.maxResults || me.defaultMaxResults),
pageSize = options.pageSize, sordZ = options.idSordZ,
result;
if (options.maxResults <= 0) {
throw "SordProvider: `options.maxResults` must be greater than 0";
}
if (options.pageSize <= 0) {
throw "SordProvider: `options.pageSize` must be greater than 0";
}
if (paging) {
result = (typeof paging === "boolean") // initialize paging
? me.pageFind((me.getFindInfoBuilder())(masks, searchFields), maxResults, pageSize, sordZ)
: me.pageFind((me.manualPagingFrom === undefined ? undefined : (me.getFindInfoBuilder())(masks, searchFields)), maxResults, pageSize, sordZ, paging); // continue paging search
} else {
me.logger.debug("Using standard search");
result = me.findAll((me.getFindInfoBuilder())(masks, searchFields), maxResults, sordZ);
}
return result;
},
searchFor: function (infos, searchCriteria, options, masks) {
var me = this, result, ids, searchResults = { idsByMask: {} };
result = me.findIds(masks, searchCriteria, options);
if ((ids = result.ids) && me.manualPagingFrom === undefined) {
infos.fetched += ids.length;
}
ids.length
&& (infos.found = true)
&& (searchResults.idsByMask[((masks.length === 1) && masks[0]) ? masks[0] : "__nomask"] = ids);
searchResults.paging = result.paging;
return searchResults;
},
searchViaIndex: function (masks, searchCriteria, options) {
var me = this, infos = {}, result;
me.logger.debug("... find by index search using search criteria", searchCriteria);
if (!me.pagingIdDefined(options.paging)) {
if (!Array.isArray(masks)) {
if (options.allowEmptyMask === true) {
me.masks = (masks = [""]);
} else {
throw "SordProvider: masks must be an array of strings!";
}
}
}
infos.fetched = 0;
result = me.searchFor(infos, searchCriteria, options, masks);
infos.pagingId && infos.found && !result.paging && me.endPaging(infos.pagingId);
return infos.found
? (
me.logger.debug("Search had " + infos.fetched + " results. " + (result.paging ? "Paging active: " + result.paging : "")),
result
)
: (me.logger.debug("No search results"), undefined);
},
addDirectValuesToSords: function (sords, instructions) {
var i = sords.length, dvKeys = instructions.dvKeys,
k, kLen = dvKeys.length, directValues = instructions.directValues,
sord, prop;
while (i--) {
sord = sords[i];
k = kLen;
while (k--) {
prop = dvKeys[k];
sord[prop] = directValues[prop];
}
}
return sords;
},
addDirectValuesToIds: function (ids, instructions) {
var fallbackIdProp = instructions.fallbackIdProp, i, iLen = ids.length, dvKeys = instructions.dvKeys,
k, kLen = dvKeys.length, directValues = instructions.directValues,
sord, prop, sords = [];
for (i = 0; i < iLen; i++) {
sord = {};
sord[fallbackIdProp] = ids[i];
k = kLen;
while (k--) {
prop = dvKeys[k];
sord[prop] = directValues[prop];
}
sords.push(sord);
}
return sords;
},
buildIdContainer: function (inputIds) {
var me = this, idContainer = { __nomask: [] };
inputIds || (inputIds = {});
if (Array.isArray(inputIds)) {
me.logger.debug("Processing ids passed via parameter. No search preceded.");
idContainer.__nomask = inputIds;
} else if (typeof inputIds.idsByMask === "object") {
me.logger.debug("Processing ids collected via search.");
idContainer = inputIds.idsByMask;
} else {
me.logger.debug("Processing a single id. No search preceded.");
idContainer.__nomask.push(inputIds); // single id from parameter
}
return idContainer;
},
processIds: function (inputIds, instructions) {
var me = this,
sords,
idContainer;
idContainer = me.buildIdContainer(inputIds);
me.finalizeCollector(instructions);
sords = me.processIdContainer(idContainer, instructions);
return (instructions.dvKeys.length)
? (
(instructions.onlyIdsInCollection
? me.addDirectValuesToIds
: me.addDirectValuesToSords
)(sords, instructions))
: sords;
},
removeEmptyFields: function (sords, props) {
var i = sords.length, k, kLen = props.length, sord, prop;
if (!kLen) {
return; // no props to check
}
while (i--) {
sord = sords[i];
k = kLen;
while (k--) {
(sord[(prop = props[k])] === "") && (sord[prop] = undefined);
}
}
},
propsToArrays: function (sords, props) {
var me = this, i = sords.length, k, kLen = props.length, sord, prop, arr,
pilcrow = me.pilcrow;
if (!kLen) {
return; // no props to check
}
while (i--) {
sord = sords[i];
k = kLen;
while (k--) {
arr = (sord[(prop = props[k])] = (sord[prop] || "").split(pilcrow));
(arr.length === 1 && arr[0] === "") && (sord[prop] = []);
}
}
},
fastFilter: function (sords, filter) {
var i = 0, iLen = sords.length, curSord, prop, k, kLen = filter.length, curFil,
match, filtered = [];
for (; i < iLen; i++) {
curSord = sords[i];
k = 0;
while (k < kLen && (match = (curFil = filter[k]).value.test(curSord[(prop = curFil.prop)]))) {
k++, curFil.exclude && (curSord[prop] = undefined);
}
match && filtered.push(curSord);
}
return filtered;
},
templateSordFilter: function (sords, filter) {
var i = 0, iLen = sords.length, curSord, k, kLen = filter.length, curFil,
match, filtered = [];
for (; i < iLen; i++) {
curSord = sords[i];
k = 0;
while (k < kLen && (match = (curFil = filter[k]).value.test(sol.common.ObjectUtils.getProp(curSord, curFil.prop)))) {
k++; // exclude does not work in templateSord mode
}
match && filtered.push(curSord);
}
return filtered;
},
filterSords: function (sords, filter) {
var me = this;
return me.formatAsTemplateSord
? me.templateSordFilter(sords, filter)
: me.fastFilter(sords, filter);
},
arrayToRegExp: function (arr, wc) {
var compl, len = arr.length,
addBitwiseOR = function (query, index, length) {
return ((index + 1 < length) && (query += "|")), query;
};
compl = arr.reduce(function (acc, str, i) {
if (typeof str !== "string") {
throw "SordProvider: only string elements are allowed for filter criteria arrays";
}
return acc + addBitwiseOR("^" + str + "$", i, len);
}, "");
return new RegExp(compl.replace(new RegExp("\\" + wc, "g"), "." + wc));
},
stringToRegExp: function (str, wc, ignoreCase) {
return new RegExp("^" + str.replace(new RegExp("\\" + wc, "g"), "." + wc), (ignoreCase ? "i" : ""));
},
contains: function (val, s) {
return !!~val.indexOf(s);
},
buildValueQuery: function (key, value, tokenized) {
var me = this;
return me.FIND_DIRECT[tokenized ? "FIELD_OBJ_KEY_TOKENIZED" : "FIELD_OBJ_KEY"]
+ key
+ ": "
+ (!me.contains(value, '"') ? '"' + value + '"' : value); // LINE_MYFIELDNAME: "My value"
},
/* This is a workaround. getContextTerms does not support wildcards.
* Therefore before executing the actual search, all available
* values for the grouping term are collected and then filtered
* for the defined value, which can contain a wildcard. */
getAvailableTerms: function (key, value, ignoreCase, findInfo, maxGroups) {
var me = this, re = me.stringToRegExp(value, me.wildCard, ignoreCase);
// search for terms of the field `key`
return me.getContextTerms(
findInfo,
me.getGroupingTerm({ groupBy: { type: "GRP", key: key } }),
(+(maxGroups) || me.defaultMaxGroups)
)
.filter(function (result) { // return only terms containing the wildcard value
return re.test(String(result.term));
});
},
valuesWithoutWildcard: function (criterion) {
var me = this;
return !Array.isArray(criterion.value)
? !me.contains(criterion.value, me.wildCard) //str
: !criterion.value.some(function (val) {
return me.contains(val, me.wildCard);
});
},
getFindInfoForConstantSearchValues: function (masks, searchCriteria, maxGroups) {
var me = this, constantCriteria = searchCriteria.filter(me.valuesWithoutWildcard.bind(me));
if (constantCriteria.length !== searchCriteria.length) {
me.logger.debug("BEGIN: Building supplementary findInfo: searchcriteria contained wildcards");
return me.buildFindInfoForContextTerms(masks, constantCriteria, maxGroups); //one time recursive call! uses all criteria without wildCards
}
},
addOR: function (query, index, length) {
return ((index + 1 < length) && (query += " OR ")), query;
},
getCriterionQuery: function (key, value, ignoreCase, tokenized, constantCriteriaFindInfo, maxGroups, params) {
var me = this,
addTermsToCriterionQuery = function (criterionQuery, term, i, availableTerms) {
var resTerm = String(term.term);
(!me.contains(resTerm, me.wildCard))
&& (criterionQuery += me.parens(
me.getCriterionQuery(
key,
me.sanitizeQueryValue(resTerm, params),
ignoreCase,
tokenized))); // recursion!
return me.addOR(criterionQuery, i, availableTerms.length);
};
return !me.contains(value, me.wildCard)
? me.buildValueQuery(key, value, tokenized)
: (
(me.getAvailableTerms(key, value, ignoreCase, constantCriteriaFindInfo, maxGroups)
.reduce(addTermsToCriterionQuery, ""))
|| me.buildValueQuery(key, value.replace(/\*/g, ""), tokenized)
);
},
addCriterionToQuery: function (maxGroups, supplementaryFindInfo, query, criterion) {
var me = this,
criterionQuery,
addCriterionQueriesForAllValuesToQuery = function (valuePartOfQuery, val, i, values) {
valuePartOfQuery += me.parens(
me.getCriterionQuery(
criterion.key,
me.sanitizeQueryValue(val, criterion),
(criterion.ignoreCase === true),
(criterion.tokenized === true),
supplementaryFindInfo,
maxGroups,
criterion
).trim()
);
return me.addOR(valuePartOfQuery, i, values.length);
};
me.logger.debug("Criterion", criterion);
if (Array.isArray(criterion.value) && !criterion.value.length) {
throw "SordProvider: " + criterion.key + ": it is not allowed to use empty arrays as a search value.";
}
criterionQuery = (Array.isArray(criterion.value) ? criterion.value : [criterion.value])
.reduce(addCriterionQueriesForAllValuesToQuery, "");
criterionQuery = Array.isArray(criterion.value) ? me.parens(criterionQuery) : criterionQuery;
me.logger.debug("Generated query for criterion:", criterionQuery);
return query += criterionQuery;
},
getSearchCriteriaQuery: function (searchCriteria, maxGroups, supplementaryFindInfo) {
var me = this;
me.logger.debug("Building query for search criteria");
return searchCriteria.reduce(me.addCriterionToQuery.bind(me, maxGroups, supplementaryFindInfo), "");
},
parens: function (s) {
return " (" + s + ") ";
},
buildRegEx: function (values) {
return new RegExp(values.join("|"), "gi");
},
buildMasksQuery: function (masks) {
var me = this;
return masks.reduce(function (masksQuery, m, i) {
return me.addOR(
(masksQuery + me.parens(me.FIND_DIRECT.FIELD_MASK_NAME + ':"' + me.sanitizeQueryValue(m) + '"')),
i,
masks.length
);
}, "");
},
escapeOrRemoveInvalidCharacters: function (val, params) {
var me = this,
result = val,
valueOrOtherwise = function (value, otherwise) {
return typeof value == 'undefined' ? otherwise : value;
},
sanitizeParams = (params || {}).sanitize || {};
sanitizeParams.escapeValues = valueOrOtherwise(sanitizeParams.escapeValues, true);
sanitizeParams.blankValues = valueOrOtherwise(sanitizeParams.blankValues, true);
sanitizeParams.removeDupWhiteSpaces = valueOrOtherwise(sanitizeParams.removeDupWhiteSpaces, true);
if (sanitizeParams.escapeValues) {
result = result.replace(me.escapeRegEx || (me.escapeRegEx = me.buildRegEx(me.ESCAPE_VALUES)), "\\$1");
}
if (sanitizeParams.blankValues) {
result = result.replace(me.removeRegEx || (me.removeRegEx = me.buildRegEx(me.BLANK_VALUES)), " ");
}
if (sanitizeParams.removeDupWhiteSpaces) {
// remove probably invalid double/tripple whitespaces
result = result.replace(me.removeDupWhiteSpaces || (me.removeDupWhiteSpaces = /\s\s+/g), " ");
}
result = result.trim();
return result;
},
sanitizeQueryValue: function (val, params) {
var me = this;
return me.escapeOrRemoveInvalidCharacters(val, params); //TODO: filterStopWords like in BS-Knowledge?
},
extendQueryByCustomQuery: function (query, custom) {
var me = this;
return me.parens(me.sanitizeQueryValue(custom)) + (query ? me.parens(query) : "");
},
extendQueryByWildCard: function (query) {
var me = this;
return me.parens(me.wildCard) + me.parens(query);
},
buildQuery: function (masks, searchCriteria, queryOpts) {
var me = this, query = "", supplementaryFindInfo;
me.logger.debug("Building search query");
me.FIND_DIRECT = ixConnect.getCONST().FIND_DIRECT;
if (!(masks.length === 1 && masks[0] === "")) {
me.logger.debug("Including masks in query", masks);
query += me.parens(me.buildMasksQuery(masks));
}
// this will be used to find all possible values of a criterion if a criterion's search value contains the wildcard
(supplementaryFindInfo = me.getFindInfoForConstantSearchValues(masks, searchCriteria, queryOpts.maxGroups))
&& me.logger.debug("END: Built supplementary findInfo");
query += me.getSearchCriteriaQuery(searchCriteria, queryOpts.maxGroups, supplementaryFindInfo);
queryOpts.customQuery
&& (query = me.extendQueryByCustomQuery(query, queryOpts.customQuery));
me.logger.debug("Built query", query);
return query
? (queryOpts.customQuery ? query : me.extendQueryByWildCard(query))
: me.wildCard;
},
buildFindDirect: function (masks, searchCriteria, queryOpts) {
var me = this, findDirect = new FindDirect();
me.logger.debug("Building findDirect");
queryOpts || (queryOpts = {});
findDirect.query = me.buildQuery(masks, searchCriteria, queryOpts).replace(/\s\s+/g, " ");
findDirect.searchInIndex = true;
if (queryOpts.customQuery) {
findDirect.searchInMemo = true;
findDirect.searchInFulltext = true;
findDirect.searchInSordName = true;
}
return findDirect;
},
buildFindInfoForContextTerms: function (masks, searchCriteria, queryOpts) {
var me = this, findInfo = new FindInfo();
me.logger.debug("Building findInfo for search via mask(s)", masks);
queryOpts || (queryOpts = {});
sol.common.ObjectUtils.type(me.query, "string")
&& (queryOpts.customQuery = String(me.query));
findInfo.findDirect = me.buildFindDirect(masks, searchCriteria, queryOpts);
return findInfo;
},
getGroupingTerm: function (fuzzy) {
if ((fuzzy.groupBy || {}).type !== "GRP") {
throw "SordProvider: Fuzzy Type `" + fuzzy.groupBy.type + "` not implemented. Supported: GRP";
}
return (fuzzy.groupBy.tokenized === true ? "tLINE." : "LINE.") + fuzzy.groupBy.key;
},
getContextTerms: function (findInfo, groupingTerm, maxGroups) {
var me = this;
me.logger.debug(["Context search term `{0}`, max results `{1}`", groupingTerm, maxGroups]);
return Array.prototype.slice.call(ixConnect.ix().getContextTerms(findInfo, groupingTerm, maxGroups));
},
castThenAddResults: function (acc, result) {
acc[result.term] = +(result.docNum);
return acc;
},
searchViaContextTerms: function (masks, searchCriteria, options) {
var me = this, maxGroups = options.fuzzy.maxGroups;
me.logger.debug("... context term search using search criteria", searchCriteria);
return me.getContextTerms(
me.buildFindInfoForContextTerms(masks, searchCriteria, { maxGroups: maxGroups }),
me.getGroupingTerm(options.fuzzy),
(+(maxGroups) || me.defaultMaxGroups)
)
.reduce(me.castThenAddResults, {});
},
performSearch: function (masks, searchCriteria, options) {
var me = this;
me.logger.debug("Starting search process using defined options");
return (
(options.fuzzy ? me.searchViaContextTerms : me.searchViaIndex).bind(me)
)(masks, searchCriteria, options);
},
optimizationIdDefined: function (optimization) {
return typeof optimization === "number";
},
pagingIdDefined: function (pagingId) {
return typeof pagingId === "string";
},
initializeCaching: function (initOptimizationCache, instructions) {
var me = this;
if (initOptimizationCache) {
me._initialOptimization = me.addInstructionsToCache("manual", instructions);
}
},
addFilterDefinition: function (instructions, filter) {
var me = this;
if (!Array.isArray(filter)) {
throw "SordProvider: filter must be an array of objects!";
}
filter.forEach(function (criterion) {
if ((typeof criterion !== "object") || (typeof criterion.prop !== "string" && criterion.prop) || ((typeof criterion.value !== "string") && (!Array.isArray(criterion.value)))) {
throw "SordProvider: filter criterion is no object, or prop or value not suited for filtering";
}
criterion.value = (typeof criterion.value === "string" ? me.stringToRegExp : me.arrayToRegExp)(criterion.value, me.wildCard);
});
instructions.filter = filter;
},
generateInstructions: function (output, filter, options) {
var me = this, instructions;
if (Array.isArray(output) && output.length > 0) {
instructions = me.parseOutputDefinition(output);
filter && me.addFilterDefinition(instructions, filter);
// add filter to instructions
me.logger.debug("Output options parsed. Generated optimized instructions:", instructions);
me.initializeCaching((me.optimize === true), instructions);
} else {
if (output.formatter && output.config && !options.fuzzy) {
instructions = {
mbs: [], // used for generating a sordZ for CheckoutSord
// defines, which values will be retrieved from the checked out sord
targetProps: [], // used when sparse option is set
converterConfig: { sordKeys: [], objKeys: [], mapKeys: {}, formBlobs: {} },
directValues: {},
dvKeys: [],
objectFormatter: {
formatter: output.formatter,
config: output.config
}
};
filter && me.addFilterDefinition(instructions, filter);
} else if (!options.fuzzy) {
throw "SordProvider: output parameter must be defined as an Array or as Object with parameter formatter and config to use ObjectFormatter";
}
me.logger.debug("Output schema will be produced by fuzzy search (getContextTerms)");
}
return instructions;
},
getInstructionsFromCache: function () {
var me = this, instructions, optimizationId = me.optimize;
if (me.optimizationIdDefined(optimizationId)) {
instructions = me.getCachedInstructions("manual", optimizationId);
me.logger.debug("Optimized Run: Using cached instructions instead of parsing output parameter");
}
return instructions;
},
initSearchAndProcessingInstructions: function (outputDefinition, filter, options, result) {
var me = this, instructions;
!(instructions = me.getInstructionsFromCache())
&& (instructions = me.generateInstructions(outputDefinition, filter, options))
&& me._initialOptimization // value has been set in generateInstructions, may be undefined if optimization is not active
&& (result.optimization = me._initialOptimization);
options.paging // restrict pageSize to max allowed value
&& (options.pageSize > me.pageSizeMax)
&& (options.pageSize = me.pageSizeMax);
options.idSordZ = (instructions.idName === "guid") ? SordC.mbOnlyGuid : SordC.mbOnlyId;
me.formatAsTemplateSord = options.formatAsTemplateSord === true;
me.ipn = options.ignorePropertyNames === true || (me.formatAsTemplateSord && options.ignorePropertyNames === undefined);
return instructions;
},
/**
* @return {Object} return
* @return {String[]|Object[]} return.sords all found and formatted sords. String[] if `output` has only one property or Object[] otherwise
* @return {Object} return.groups map of the group by field values and their result counts
* @return {String} return.paging the searchId which can be again passed to this service as `options.paging` parameter to continue the search
* @return {Number} return.optimization the optimizationId which can be again passed to this service as `optimize`
*/
process: function () {
var me = this, processingInstructions, deliverables, result = {},
outputDefinition = me.output, options = (me.options || {}),
filter = me.filter, fuzzySearch = !!options.fuzzy,
pagingOpt = (options.paging || (options.paging = options.searchId));
if (me.ids && me.id) {
throw "SordProvider: you can only pass the `ids` or the `id` parameter, but not both at the same time.";
}
if ((options.endPaging === true)) {
if (!me.pagingIdDefined(pagingOpt)) {
throw "SordProvider: `endPaging:true` has been defined. However, no searchId was passed.";
}
me.closeFind(pagingOpt);
return { sords: [] };
}
if (options.startPagingFrom !== undefined) {
me.manualPagingFrom = options.startPagingFrom;
}
if (sol.common.ObjectUtils.type(options.query, "string")) {
me.query = options.query;
}
if (options.findDirect || me.query) { // will use findDirect instead of findByIndex
me.findDirect = true;
}
!fuzzySearch && // no preparations required for a fuzzy search
(processingInstructions = me.initSearchAndProcessingInstructions(outputDefinition, filter, options, result));
deliverables = (me.search || me.pagingIdDefined(pagingOpt))
? me.performSearch(me.masks, me.search, options) // findByIndex or getContextTerms (fuzzy) search
: (me.id || me.ids); // use specified ids instead of searching
if (fuzzySearch) { // only stores results. no processing for fuzzy search results
me.logger.debug("Returning fuzzy search results (groups)");
result.groups = deliverables;
} else {
result.sords = [];
deliverables
&& (result.sords = me.processIds(deliverables, processingInstructions)) // apply output rules
&& deliverables.paging // if the search was paged and can be continued ...
&& (result.searchId = deliverables.paging); // ... store pagingId in result
me.moreResults && (result.moreResults = true);
if (result.sords && typeof result.sords[0] === "object") {
processingInstructions.filter
&& (result.sords = me.filterSords(result.sords, processingInstructions.filter));
options.sparse
&& me.removeEmptyFields(result.sords, processingInstructions.targetProps);
Array.isArray(options.propsAsArrays)
&& me.propsToArrays(result.sords, options.propsAsArrays);
}
}
return result;
}
});
/**
* This class contains util functions to facilitate working with the SordProvider.
* E.g. run, which takes a configuration and returns processed sords.
* runOptimized which takes a configuration, a cacheObject (this should be a persistent
* object in your class) and a optimization name. Additionally to the processed sords,
* an "optimization" id will be returned. This id will automatically get stored at
* cacheObject[optimizationName].
*
* The passed config will be stringified and parsed to create a copy of it. This reduces
* bugs. If you already pass a copied configuration into this function, you can disable
* the copy mechanism by defining { copy: false } as the fourth parameter.
* @elojc
* @eloas
* @eloix
*/
sol.define("sol.common.SordProviderUtils", {
singleton: true,
runOptimized: function (config, cacheObject, optimizationName, opts) {
var me = this, result;
opts || (opts = {});
try {
config = config ? (opts.copy === false ? config : JSON.parse(JSON.stringify(config))) : {};
} catch (e) {
throw "SordProviderUtils: configuration object could not be converted to JSON. Do not pass java objects/strings to SordProvider! " + e;
}
if (typeof cacheObject !== "object") {
throw "SordProviderUtils: the second argument to runOptimized must be a persistent object. Optimization ids will be stored here.";
}
if (!optimizationName) {
throw "SordProviderUtils: the third argument to runOptimized must be a unique string (name) for the optimization. The name will be used for referencing a stored optimization id.";
}
if (config.optimize = cacheObject[optimizationName]) { // exclude unnecessary properties from config
config["output"] = undefined;
config["filter"] = undefined;
} else {
config.optimize = true;
}
cacheObject[optimizationName] = (result = me.run(config, { copy: false })).optimization; // store optimization ID in cache
return result;
},
run: function (config, opts) {
opts || (opts = {});
try {
config = config ? (opts.copy === false ? config : JSON.parse(JSON.stringify(config))) : {};
} catch (e) {
throw "SordProviderUtils: configuration object could not be converted to JSON. Do not pass java objects/strings to SordProvider!" + e;
}
return (sol.create("sol.common.SordProvider", config)).process(config || {});
},
create: function (config) {
return sol.create("sol.common.SordProviderBuilder", config || {});
}
});
/**
* This class is in experimental mode and is not allowed to used from external source.
*
* Only for internal purposes currently.
* @experimental
* @private
*/
sol.define("sol.common.SordProviderBuilder", {
_DEFAULT_FORMATTER: "sol.common.ObjectFormatter.TemplateSord",
initialize: function (config) {
var me = this;
me.ids = {};
me.masks = [];
me.search = {};
me.output = {};
me.options = config.options || {};
me.$super("sol.Base", "initialize", [config]);
return me;
},
/**
*
* @param {*} masks
* @returns
*/
addMasks: function (masks) {
var me = this;
sol.common.ObjectUtils
.toArray(masks)
.filter(function (mask) {
return sol.common.ObjectUtils.isString(mask);
})
.forEach(function (mask) {
// add mask only if the mask not already exists.
me.masks.indexOf(mask) == -1 && me.masks.push(mask);
});
return me;
},
useId: function (id) {
var me = this;
if (!sol.common.RepoUtils.isRepoId(id)) {
throw Error("`id`=" + id + " is not a valid elo object identifier. See checkoutSord documentation");
}
me.ids[id] = true;
return me;
},
useIds: function (ids) {
var me = this;
sol.common.ObjectUtils.toArray(ids)
.forEach(function (id) {
me.useId(id);
});
return me;
},
/**
* @param type {String}
*/
addSearchCriteria: function (key, values, type) {
var me = this;
if (!me.search[key]) {
me.search[key] = {
values: sol.common.ObjectUtils.toArray(values),
type: type
};
} else {
// already exist, append new values
sol.common.ObjectUtils
.toArray(values)
.forEach(function (val) {
me.search[key].values.push(val);
});
}
return me;
},
addDateSearchCriteria: function (key, value) {
var me = this;
me.addSearchCriteria(key, value, "date");
return me;
},
addDateRangeCriteria: function (key, start, end) {
var me = this,
rangeStr;
rangeStr = (start || "") + "..." + (end || "");
me.addSearchCriteria(key, rangeStr, "date");
return me;
},
addOutput: function (source, target) {
var me = this, outputDef, outputIdentifier;
if (!(source.type || source.key)) {
throw Error("source must have structure of {type, key}");
}
outputIdentifier = source.type + source.key;
if (!me.output[outputIdentifier]) {
me.output[outputIdentifier] = { source: source, target: target };
} else {
outputDef = me.output[outputIdentifier];
if (outputDef.target.prop !== target.prop) {
// ignore output if it is the same...
// If it is not equals we have two fields with different names
// that should not be possible
throw Error("Output definition already exists for " + JSON.stringify(source) + " `target.prop` may not be different here");
}
}
return me;
},
addOutputs: function (outputs) {
var me = this;
(outputs || []).forEach(function (output) {
me.addOutput(output.source, output.target);
});
return me;
},
get: function () {
// hide internal date so we want to copy relevant config data
var me = this, config = {};
if (!sol.common.ObjectUtils.isEmpty(me.ids)) {
config.ids = me.mapIds();
} else {
config.masks = me.masks;
config.search = me.mapToSearchCriteria();
}
config.output = me.mapOutput();
config.options = me.options;
return config;
},
run: function (options) {
var me = this;
return (me.optimizationName && me.optimizationCache)
? sol.common.SordProviderUtils.runOptimized(me.get(), me.optimizationCache, me.optimizationName, options)
: sol.common.SordProviderUtils.run(me.get(), options);
},
mapToSearchCriteria: function () {
var me = this;
return Object.keys(me.search)
.map(function (key) {
var searchCriteria = me.search[key],
searchCriteriaObj = { key: key, value: searchCriteria.values };
searchCriteria.type && (searchCriteriaObj.type = searchCriteria.type);
return searchCriteriaObj;
});
},
mapOutput: function () {
var me = this;
if (sol.common.ObjectUtils.isEmpty(me.output)) {
return {
formatter: me._DEFAULT_FORMATTER,
config: {}
};
} else {
return Object.keys(me.output)
.map(function (key) {
return me.output[key];
});
}
},
mapIds: function () {
var me = this;
return Object.keys(me.ids)
.filter(function (id) {
return !!me.ids[id];
})
.map(function (id) {
return id;
});
}
});