    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.Template.js
    //@include lib_sol.common.Locale.js
    //@include lib_sol.common.HttpUtils.js
    //@include lib_sol.common.ix.DynKwlUtils.js

    /**
     * BLP Query Iterator used by dynamic keyword lists. This class provides an abstract layer that simplifies the usage
     * of keywording information.
     *
     * This iterator has to be used in combination with a BLP query. When used in a GenericDynKwl we're extracting
     * the correct BLP values (e.g. queryName, addInId, moduleId, ...) within the DynKWL configuration app.
     * When implemented via code you'll need to provide the specific properties yourself.
     *
     * # Example implementation
     *
     *
     * Implementation of an Index Server data iterator that can be used by keywording forms:
     *
     *    sol.define("sol.examples.ix.dynkwl.Customers", {
     *      extend: "sol.common.ix.DynKwlBLPIterator",
     *
     *      tableTitle: "Customers of a ERP system",
     *      tableKeyNames: ["CUSTOMER_NO", "CUSTOMER_NAME", "IX_MAP_CUSTOMER_CATEGORY"],
     *      serverUrl: "http://10.1.2.10:30009",
     *      appToken: "HThYifwwAsdfYf400XSaiINismui3kTDOPqan8EoyfM=",
     *      projectId: "603c98f95675da5172eb84cb",
     *      queryName: "psql.navcustomers",
     *      queryModule: "OleDb",
     *      moduleId: "a7f28149-7202-43f2-b3ec-d225743b06ee",
     *      addInId: "da414d55-45b5-451a-a03a-9517727ae9c7",
     *      queryConditions: [
     *        { name: "IDX1", fieldName: "CUSTOMER_NAME" },
     *        { name: "IDX2", value: "IMPORTANT CUSTOMERS" }
     *      ]
     *    });
     *
     *    function getDataIterator() {
     *      var iterator;
     *      iterator = sol.create("sol.examples.ix.dynkwl.Customers", {  });
     *      return new DynamicKeywordDataProvider(iterator);
     *    }
     *
     * @author ELO Digital Office GmbH
     *
     * @eloix
     * @requires sol.common.StringUtils
     * @requires sol.common.SordUtils
     * @requires sol.common.Template
     * @requires sol.common.Locale
     * @requires sol.common.HttpUtils
     * @requires sol.common.ix.DynKwlUtils
     */
    sol.define("sol.common.ix.DynKwlBLPIterator", {
      mixins: ["sol.common.ix.DynKwlMixin"],

      /**
       * @cfg {string} serverUrl
       * URL of BLP server (e.g. http://<IP Adress>:30009)
       */
      serverUrl: undefined,
      /**
       * @cfg {string} appToken
       * App token for BLP connection. Needs to be configured in the BLP designer within the Credential Store
       * This is the "Secret Key" within the "Access Keys"
       * (e.g. HThYifwwAsdfYf400XSaiINismui3kTDOPqan8EoyfM=)
       */
      appToken: undefined,
      /**
       * @cfg {string} projectId
       * Project Id for this BLP query. Can be the project name
       */
      projectId: undefined,
      /**
       * @cfg {string} tableTitle
       * name of this table. The title is displayed by the client.
       */
      tableTitle: undefined,
      /**
       * @cfg {string} queryName
       * Name of the BLP query to be used in this DynKWL
       */
      queryName: undefined,
      /**
       * @cfg {string} queryModule
       * Name of the BLP module (e.g.: "OleDb") to be used in this DynKWL
       */
      queryModule: undefined,
      /**
       * @cfg {string} moduleId
       * ID of the BLP module (e.g.: "a7f28149-7202-43f2-b3ec-d225743b06ee for the DataConnect module") to be used in this DynKWL
       */
      moduleId: undefined,
      /**
       * @cfg {string} addInId
       * ID of the BLP addIn (e.g.: "da414d55-45b5-451a-a03a-9517727ae9c7") to be used in this DynKWL
       */
      addInId: undefined,
      /**
       * @cfg {Array} queryConditions
       *
       *    [{ "name": "IDX1", "value": "20000", message: 'field x missing' }]
       *
       * Conditions to be passed to the BLP query
       * If no name is provied the current focuesd field is used.
       */
      queryConditions: [],
      /**
       * @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: "",

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

        if ((!me.serverUrl && !config.serverUrl)
          || (!me.appToken && !config.appToken)
          || (!me.projectId && !config.projectId)
          || (!me.queryName && !config.queryName)
          || (!me.queryModule && !config.queryModule)
          || (!me.moduleId && !config.moduleId)
          || (!me.addInId && !config.addInId)
          || (!me.tableKeyNames && !config.tableKeyNames)
          || (!me.tableHeaders && !config.tableHeaders)) {
          me.log.error("Dynamic keyword list: serverUrl, queryName, queryModule, projectId, moduleId, addInId, tableKeyNames and tableHeaders must be set.");
        }

        // this is the BLP security token to authenticate with the service
        me.serverUrl = config.serverUrl || me.serverUrl;
        me.appToken = config.appToken || me.appToken;
        me.projectId = config.projectId || me.projectId;
        me.queryName = config.queryName || me.queryName;
        me.queryModule = config.queryModule || me.queryModule;
        me.moduleId = config.moduleId || me.moduleId;
        me.addInId = config.addInId || me.addInId;

        queryUrl = "{{serverUrl}}/api/v1/project/{{projectId}}/dataquery/{{moduleId}}/{{addInId}}/execute";

        tpl = sol.create('sol.common.Template', {
          source: queryUrl
        });

        me.blpQueryUrl = tpl.apply({
          serverUrl: me.serverUrl,
          projectId: me.projectId,
          moduleId: me.moduleId,
          addInId: me.addInId
        });

        me.tableTitle = config.tableTitle || me.tableTitle;
        me.queryConditions = config.queryConditions || me.queryConditions;
        me.tableKeyNames = config.tableKeyNames || me.tableKeyNames;
        me.tableHeaders = config.tableHeaders || me.tableHeaders;
        me.focusFieldGivesValueForMap = !!config.focusFieldGivesValueForMap;

        me.errorMessage = "";
        me.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) {
        var me = this;
        me.log.enter("open", { sord: sord, fieldName: fieldName });

        me._keyNames = me.getTableKeyNames(fieldName);

        me.index = 0;
        me.resultSet = me.getQueryResults(me.createBLPConditionFields(ec, fieldName, null, null, sord));
        me.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 me = this, fieldIndex;
        me.log.enter("openMap", { focusName: focusName, map: map });

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

        me.index = 0;
        me.resultSet = me.getQueryResults(me.createBLPConditionFields(ec, focusName, fieldIndex, map, null));
        me.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;
        }
      },

      /**
       * Within BLP we're basically just passing our result row to our DynKwl.
       * You may overwrite this function for some fancy row preparation
       * @param {String[]} row
       * @return {String[]} Table row
       */
      getRowData: function (row) {
        return row;
      },

      /**
       * 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 results from a BLP query.
       * @return {de.elo.ix.client.Sord[]}
       */
      getQueryResults: function (queryConditions) {
        var me = this, responseObj, dataObj, results, des, decryptedAppToken;
        me.log.enter("getSearchResults", this._findInfo);

        dataObj = {
          name: me.queryName,
          module: me.queryModule
        };

        if (queryConditions.length > 0) {
          dataObj.condition_fields = queryConditions;
        }

        des = new Packages.de.elo.utils.sec.DesEncryption();
        decryptedAppToken = des.decrypt(me.appToken);
        requestProperties = {
          Authorization: "Bearer " + decryptedAppToken
        };

        responseObj = sol.common.HttpUtils.sendRequest({
          url: me.blpQueryUrl,
          method: "post",
          connectTimeout: 10000,
          readTimeout: 60000,
          dataObj: dataObj,
          encodeData: false,
          requestProperties: requestProperties
        });

        results = JSON.parse(responseObj.content);

        if (!results.result || !results.result.rows) {
          throw new Exception("No BLP Query results returned. Content: " + responseObj.content);
        }
        me.log.exit("getQueryResults");

        return results.result.rows;
      },

      /**
       * Internal function that utilizes the creation of condition_fields passed to the BLP query.
       *
       * @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
       */
      createBLPConditionFields: function (ec, focusField, fieldIndex, map, sord) {
        var me = this, queryConditions = [],
          i, param, fieldName, value, mapValueField;
        me.log.enter("createBLPConditionFields", { focusField: focusField, fieldIndex: fieldIndex, map: map, sord: sord });

        me.errorMessage = "";

        for (i = 0; i < me.queryConditions.length; i++) {
          param = me.queryConditions[i];

          if (!param.value) {
            fieldName = param.fieldName || focusField;
            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);
              }
              param.value = (map[mapValueField] || map["IX_GRP_" + mapValueField]) || "";
            } else {
              param.value = sol.common.SordUtils.getObjKeyValue(sord, fieldName) || "";
            }
          }

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

          if (param.value || ((!param.value || param.value == "") && param.emptyAllowed === true)) {
            queryConditions.push(param);
          }
        }

        me.log.exit("createBLPConditionFields");
        return queryConditions;
      },

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