    importPackage(Packages.de.elo.ix.client);
    importPackage(Packages.de.elo.ix.jscript);
    importPackage(Packages.de.elo.ix.scripting);

    //@include lib_Class.js
    //@include lib_sol.common.StringUtils.js
    //@include lib_sol.common.SordUtils.js
    //@include lib_sol.common.UserProfile.js
    //@include lib_sol.common.Locale.js
    //@include lib_sol.common.ix.DynKwlUtils.js

    /**
     * Search Iterator used by dynamic keyword lists. This class provides an abstract layer that simplifies the usage
     * of keywording information.
     *
     * If child elements should be returned please refer to the subclass sol.common.ix.DynKwlFindChildrenIterator
     * which implements that scenario. In most cases it is recommended to implement a specific scenario by a generalized
     * subclass.
     *
     * A DynKwlSearchIterator required an Index Server FindInfo object in order to find the required sord elements.
     * Thanks to a function `getRowData` the result of one row can be defined by keywording information.
     *
     * # Example implementation
     *
     * Following example shows the implementation of an iterator that returns child elements of a given parent.
     *
     *     // script: sol.common.ix.DynKwlFindChildrenIterator.js
     *
     *     sol.define('sol.common.ix.DynKwlFindChildrenIterator', {
     *       extend: 'sol.common.ix.DynKwlSearchIterator',
     *       tableHeaders: ["Name", "Description"],
     *       parentId: null,
     *       searchParams: [{mode: 'STARTS_WITH'}],
     *
     *       initialize: function (config) {
     *         config = config || {};
     *         this.parentId = config.parentId || this.parentId;
     *
     *         this.$super('sol.common.ix.DynKwlSearchIterator', 'initialize', arguments);
     *       },
     *
     *       // implement getFindInfo.
     *       getFindInfo: function(filterList) {
     *     	   var findInfo,
     *     	     findChildren, findByIndex;
     *
     *     	   findInfo = new FindInfo();
     *     	   findChildren = new FindChildren();
     *     	   findChildren.parentId = this.parentId;
     *         findInfo.findChildren = findChildren;
     *
     *         if (filterList && filterList.length > 0) {
     *           findByIndex = new FindByIndex();
     *           findByIndex.name = filterList[0];
     *           findInfo.findByIndex = findByIndex;
     *         }
     *
     *         return findInfo;
     *       },
     *
     *       getRowData: function(sord) {
     *     	   return [sord.name, sord.desc];
     *       }
     *     });
     *
     * Implementation of an Index Server data iterator that can be used by keywording forms:
     *
     *     // script: sol.pubsec.ix.dynkwl.generators.NameFile.js
     *
     *     sol.define('sol.pubsec.ix.dynkwl.generators.NameFile', {
     *       extend: 'sol.common.ix.DynKwlFindChildrenIterator',
     *
     *       tableTitle: 'Generators - File Name',
     *       tableKeyNames: ["FILE_NAME_GEN", null],
     *       parentId: '123'
     *
     *     });
     *
     *     function getDataIterator() {
     *       var iterator;
     *       iterator = sol.create('sol.pubsec.ix.dynkwl.generators.NameFile', {  });
     *       return new DynamicKeywordDataProvider(iterator);
     *     }
     *
     * @author NM, ELO Digital Office GmbH
     * @version 1.03.000
     *
     * @eloix
     * @requires sol.common.StringUtils
     * @requires sol.common.SordUtils
     * @requires sol.common.UserProfile
     * @requires sol.common.Locale
     * @requires sol.common.ix.DynKwlUtils
     */
    sol.define("sol.common.ix.DynKwlSearchIterator", {
      mixins: ["sol.common.ix.DynKwlMixin"],

      /**
       * @cfg {string} tableTitle
       * name of this table. The title is displayed by the client.
       */
      tableTitle: undefined,
      /**
       * @cfg {de.elo.ix.client.SordZ} sordZ
       * Defines which information to retrieve from the search. e.g. `SordC.mbAll`. Default is `SordC.mbAllIndex`.
       */
      searchSordZ: SordC.mbAllIndex,
      /**
       * @cfg {Array} searchParams
       * Filter values that can be used for creating a FindInfo search definition.
       *
       *     [{ name: 'FILING_PLAN_REFERENCE', mode: 'STARTS', message: 'field x missing' }]
       *
       * if no name is provided current focused field is used.
       * These values are passed to the `getFindInfo` function.
       */
      searchParams: null,
      /**
       * @cfg {Number} searchCount
       * Number of search results to retrieve.
       * Please mind that results should be filtered by the user input.
       * Displaying more than 100 results is not recommended.
       */
      searchCount: 100,
      /**
       * @cfg {Array} tableKeyNames (required)
       * mapping between columns and elo group or map fields. if null, value is not transferred.
       *
       *     // GRP field     map static field           map table field
       *     ['RECORD_REFERENCE', null];
       *
       * Map fields must contain IX_MAP as a prefix and can optionally use {i} as a counter value.
       */
      tableKeyNames: undefined,
      /**
       * @cfg {Array} tableHeaders (required)
       * column header names. if null column is hidden in the client.
       *
       *     ["Name", "Description"]
       */
      tableHeaders: undefined,
      /**
       * @property
       * @private
       * Error message that is passed to the client if value is missing.
       */
      errorMessage: "",

      /**
       * @private
       * created findInfo object.
       */
      _findInfo: null,

      initialize: function (config) {
        this.log = sol.create("sol.Logger", { scope: this.$className || "sol.common.ix.SearchIterator" });
        this.log.enter("initialize", config);
        config = config || {};

        if ((!this.searchParams && !config.searchParams)
          || (!this.tableKeyNames && !config.tableKeyNames)
          || (!this.tableHeaders && !config.tableHeaders)) {
          this.log.error("Dynamic keyword list: searchParams, keyNameTemplate and tableHeaders must be set.");
        }

        this.tableTitle = config.tableTitle || this.tableTitle;
        this.searchParams = config.searchParams || this.searchParams;
        this.searchSordZ = config.searchSordZ || this.searchSordZ;
        this.searchCount = config.searchCount || this.searchCount;
        this.tableKeyNames = config.tableKeyNames || this.tableKeyNames;
        this.tableHeaders = config.tableHeaders || this.tableHeaders;
        this.focusFieldGivesValueForMap = !!config.focusFieldGivesValueForMap;

        this.connection = config.connection || ixConnect;

        this.errorMessage = "";
        this.log.exit("initialize");
      },

      /**
       * Opens a connection for the elo java client and non map field capable clients.
       *
       * @param {de.elo.ix.scripting.ScriptExecContext} ec
       * @param {de.elo.ix.client.Sord} sord working version of the current sord object
       * @param {String} fieldName name of the currently focused field
       */
      open: function (ec, sord, fieldName) {
        this.log.enter("open", { sord: sord, fieldName: fieldName });

        this._keyNames = this.getTableKeyNames(fieldName);
        this.focusValue = sol.common.SordUtils.getObjKeyValue(sord, fieldName) || "";

        this.index = 0;
        this._findInfo = this.getFindInfo(this.createSearchFilterList(ec, fieldName, null, null, sord));
        this.resultSet = this.getSearchResults();
        this.log.exit("open");
      },

      /**
       * Opens a connection for elo wf forms and map field capable components
       *
       * @param {de.elo.ix.scripting.ScriptExecContext} ec
       * @param {Object} map Map of all entries passed by the client
       * @param {String} focusName Name of the currently focused field
       */
      openMap: function (ec, map, focusName) {
        var fieldIndex;

        this.log.enter("openMap", { focusName: focusName, map: map });

        this.focusValue = map[focusName] || "";

        fieldIndex = this.getIndexFromName(focusName);
        this._keyNames = this.getTableKeyNames(focusName).map(function (keyName) {
          return !!keyName ? ((fieldIndex != "") ? keyName.replace("{i}", fieldIndex) : keyName) : null;
        });

        this.index = 0;
        this._findInfo = this.getFindInfo(this.createSearchFilterList(ec, focusName, fieldIndex, map, null));
        this.resultSet = this.getSearchResults();
        this.log.exit("openMap");
      },

      /**
       * Closes the connection for both map and non map capable clients.
       */
      close: function () {
        this.log.enter("close");
        this._findInfo = null;
        this.log.exit("close");
      },

      /**
       * Returns the next row of the table.
       *
       * @return {String[]} Table row
       */
      getNextRow: function () {
        var row = this.resultSet[this.index++];
        if (row) {
          row = this.getRowData(row);
          this.formatRow(row);
          return row;
        }
      },

      /**
       * @abstract
       * This function must be implemented by the child class. It should return the data as string[] for one table row.
       * @param {de.elo.ix.client.Sord} sord
       * @return {String[]} Table row
       */
      getRowData: function (sord) {
        throw this.$class + ": getRowData must be implemented by child class.";
      },

      /**
       * @abstract
       * This function must be implemented by the child class. It should return an Index Server FindInfo object that is used by the search.
       *
       *     getFindInfo: function(filterList) {
       *       this.log.enter("getFindInfo");
       *       var findInfo,
       *       findChildren, findByIndex;
       *
       *       findInfo = new FindInfo();
       *       findChildren = new FindChildren();
       *       findChildren.parentId = this.parentId;
       *       findInfo.findChildren = findChildren;
       *
       *       if (filterList && filterList.length > 0) {
       *         findByIndex = new FindByIndex();
       *         findByIndex.name = filterList[0];
       *         findInfo.findByIndex = findByIndex;
       *       }
       *
       *       this.log.exit("getFindInfo");
       *       return findInfo;
       *     },
       *
       * @param {String[]} filter
       * @return {de.elo.ix.client.FindInfo}
       */
      getFindInfo: function (filter) {
        throw this.$class + ": getFindInfo must be implemented by child class.";
      },

      /**
       * Returns the header of this table that can be displayed by the clients.
       *
       * @return {String[]} Table header
       */
      getHeader: function () {
        return this.tableHeaders;
      },

      /**
       * Returns the keys of this table that can be used in order to map
       * map or group fields with columns.
       *
       * @return {String[]} Table keys
       */
      getKeyNames: function () {
        return this._keyNames;
      },

      /**
       * Returns true if table has more rows.
       *
       * @return {Boolean} Has more rows
       */
      hasMoreRows: function () {
        return (this.index < (this.resultSet.length));
      },

      /**
       * Returns the error message that should be displayed by the client
       * instead of the table data.
       *
       * @return {String} error message
       */
      getMessage: function () {
        return this.errorMessage;
      },

      /**
       * Returns a title for this table used by the user interface.
       *
       * @return {String} Title
       */
      getTitle: function () {
        return this.tableTitle;
      },

      /**
       * @private
       * Retrieves a list of search results based on the given FindInfo object. Result list is limited to 100 items.
       * @return {de.elo.ix.client.Sord[]}
       */
      getSearchResults: function () {
        this.log.enter("getSearchResults", this._findInfo);
        var findResult, sords;

        findResult = this.connection.ix().findFirstSords(this._findInfo, this.searchCount, this.searchSordZ);
        sords = findResult.sords || [];

        this.connection.ix().findClose(findResult.searchId);
        this.log.info("found sords: " + sords.length);

        this.log.exit("getSearchResults");

        return sords;
      },

      /**
       * Internal function that utilizes the creation of filters used by the creation of findInfo objects.
       *
       * @param {de.elo.ix.scripting.ScriptExecContext} ec IX ScriptExecContext
       * @param {String} focusField Currently focused field
       * @param {String} fieldIndex
       * @param {Object} map Map of all entries passed by the client
       * @param {de.elo.ix.client.Sord} sord working version of the current sord object
       * @returns {String[]} Values for prepared statement
       */
      createSearchFilterList: function (ec, focusField, fieldIndex, map, sord) {
        this.log.enter("createSearchFilterList", { focusField: focusField, fieldIndex: fieldIndex, map: map, sord: sord });
        var list = [],
            i, param, fieldName, value, mapValueField;

        this.errorMessage = "";

        this.log.debug("defined searchParams: ", this.searchParams);

        for (i = 0; i < this.searchParams.length; i++) {
          param = this.searchParams[i];
          fieldName = param.name || focusField;

          if (param.value) {
            value = param.value;
          } else if (param.valueType) {
            switch (param.valueType) {
              case "LANGUAGE":
                value = ec.ci.language;
                break;
              default:
                value = "";
            }
          } else if (map) {
            // WF currently passes IX_GRP for group fields.
            // This must be fixed so SQL-Queries can be used in mixed mode.
            mapValueField = this.focusFieldGivesValueForMap ? focusField : fieldName;
            if (fieldIndex) {
              mapValueField = String(mapValueField).replace("{i}", fieldIndex);
            }
            value = (map[mapValueField] || map["IX_GRP_" + mapValueField]) || "";
          } else {
            value = sol.common.SordUtils.getObjKeyValue(sord, fieldName) || "";
          }

          if (!value && param.message) {
            this.errorMessage = param.message;
          }

          if ((param.emptyAllowed === true) && (!value || value == "")) { // BSVM-266, BSVM-270 allow empty values
            list.push(null);
          } else {
            switch (param.mode) {
              case "STARTS_WITH":
                list.push(value + "*");
                break;
              case "CONTAINS":
                list.push("*" + value + "*");
                break;
              case "ENDS_WITH":
                list.push("*" + value);
                break;
              default:
                list.push(String(value));
            }
          }

        }
        this.log.exit("createSearchFilterList");
        return list;
      },

      getIndexFromName: function (name) {
        name = String(name);
        if (!name) {
          return "";
        }
        var pos = name.search(/\d+$/);
        if (pos > 0) {
          return name.substring(pos);
        }
        return "";
      }
    });