    //@include lib_Class.js
    //@include lib_sol.common.ObjectUtils.js

    /**
     * Roles can be used in order to identify a proper user e.g. in a workflow.
     *
     * E.g. in an invoice approval process a user should be added to the approval process if the invoice amount exceeds a
     * defined value. In this scenario, the management group should approve invoices that are more expansive than 100000€.
     *
     * Contains the functions used to determine the correct users using a configuration object. If more then one user will be suitable, the configuration order will be maintained.
     *
     * # Configuration
     * ## Example
     *
     * The configuration is an Array which contains Objects containing the rolenames as `name` property and an `users` property which is an Array of user configurations.
     * This user configuration objects have at least a `user` property, which is the ELO username. Additionally each object can contain several conditions, which are defined thanks to an Array.
     *
     *     var myRoleConfig = [
     *       {
     *         "name": "ROLE_1",
     *         "users": [
     *           { user : "user1", conditions: [ { type: "GRP", key: "AMOUNT", rel: "GT", val: 3000, dataType: "number" } ] },
     *           { user : "user2" }
     *         ]
     *       },
     *       {
     *         name: "ROLE_2",
     *         users: [
     *           { user: "user2", conditions: [ { type: "GRP", key: "RECIPIENT_NO", rel: "EQUALS", val: "4713"} ] },
     *           { user: "group1" }
     *         ]
     *       },
     *       {
     *         name: "ROLE_3",
     *         users: [
     *           { user: { type: "GRP", key: "PO_PURCHASE_USER", mode: "SUPERVISOR", supervisorLevel: 2 }, conditions: [ { type: "GRP", key: "AMOUNT", rel: "GE", val: "4714"} ] },
     *         ]
     *       }
     *     ]
     *
     * ## Conditions
     * Conditions are optional and are used to limit the users depending on the values in an Sord object. Conditions are an Array of Objects.
     *
     *     {
     *       // type of field (GRP|SORD)
     *       type: "GRP",
     *       // name of the index group, or sord property
     *       key : "{ELO OBJEKY GROUPNAME}",
     *       // relation for the check
     *       rel : "{RELATION}",
     *       // value to check for
     *       val : "{VALUE}"
     *     }
     *
     * If conditions are configured, all of them must meet the requirements.
     *
     * Supported relations are:
     *
     *  - `GT`: value is greater than `x`
     *  - `GE`: value is greater or equal than `x`
     *  - `LT`: value is lower than `x`
     *  - `LE`: value is lower or equal than `x`
     *  - `EQUALS`: value is equal to `x`
     *
     * # Retrieving a user list
     *
     *     var sord = ixConnect.ix().checkoutSord(...);
     *     var users = sol.common.Roles.getUsers('MYROLE', sord, myRoleConfig);
     *
     * @author PZ, ELO Digital Office GmbH
     * @version 1.03.000
     *
     * @eloall
     * @requires handlebars
     * @requires sol.common.Template
     * @requires sol.common.SordUtils
     *
     */
    sol.define("sol.common.Roles", {
      singleton: true,

      /**
       * @private
       * Contains the calculation rules.
       */
      fct: {
        EQUALS: function (param1, param2) {
          return param1 == param2;
        },
        GT: function (param1, param2) {
          return param1 > param2;
        },
        GE: function (param1, param2) {
          return param1 >= param2;
        },
        LT: function (param1, param2) {
          return param1 < param2;
        },
        LE: function (param1, param2) {
          return param1 <= param2;
        },
        NOT: function (param1, param2) {
          return param1 != param2;
        },
        STARTSWITH: function (param1, param2) {
          param1 = (param1 || "") + "";
          param2 = (param2 || "") + "";
          return (param1.indexOf(param2) == 0);
        }
      },

      /**
       * This function evaluates a configuration to find all suitable users for a role.
       * @param {String|Object} role Role name. If this is defined as an object, a `type` and a `key` has to be defined to load the role from the sords metadata (see {@link sol.common.SordUtils#getValue}).
       * @param {de.elo.ix.client.Sord} sord Sord to check conditions from config
       * @param {Object[]} config Configuration object
       * @return {String[]} Array of usernames (or an empty Array)
       *
       */
      getUsers: function (role, sord, config) {
        var me = this,
            users, result;
        me.logger.enter("getUsers", arguments);

        users = me.getUsers2(role, sord, config);

        result = users.map(function (entry) {
          return entry.name;
        });

        me.logger.exit("getUsers", result);
        return result;
      },

      /**
       * This function evaluates a configuration to find all suitable users for a role.
       * getUser2() considers the property 'mandatory'
       *
       * @param {String|Object} role Role name. If this is defined as an object, a `type` and a `key` has to be defined to load the role from the sords metadata (see {@link sol.common.SordUtils#getValue}).
       * @param {de.elo.ix.client.Sord} sord Sord to check conditions from config
       * @param {Object[]} originalConfig Configuration object
       * @return {Array} Array of user entries
       *
       */
      getUsers2: function (role, sord, originalConfig) {
        var me = this,
            applicableRules = [],
            userEntries = [],
            config, i, j, rules, rule, conditions, condition, conditionResult, allConditionsMeet, value,
            userName, sordMap, userLogString, applicableRule, userEntry;

        config = sol.common.ObjectUtils.clone(originalConfig);

        sordMap = sol.create("sol.common.SordMap", { objId: sord.id });
        sordMap.read();

        me.logger.enter("getUsers2", arguments);

        role = me.retrieveRole(role, config, sord);
        rules = me.getUsersByRole(role, config);

        if (rules && rules.length > 0) {
          for (i = 0; i < rules.length; i++) {
            rule = rules[i];
            conditions = rule.conditions;

            userLogString = sol.common.ObjectUtils.isObject(rule.user) ? JSON.stringify(rule.user) : rule.user;

            if (conditions && conditions.length > 0) {
              allConditionsMeet = true;
              for (j = 0; j < conditions.length; j++) {
                condition = conditions[j];
                value = me.getValue(sord, sordMap, condition);
                conditionResult = this.fct[condition.rel](value, condition.val);
                me.logger.debug(["getUser2: Condition: sord.id={0}, sord.name={1}, user={2}, condition.type={3}, condition.key={4}, value={5}, condition.rel={6}, condition.val={7}, condition.val.type={8}, result={9}", sord.id, sord.name, userLogString, condition.type, condition.key, value, condition.rel, condition.val, typeof condition.val, conditionResult]);
                if (!conditionResult) {
                  allConditionsMeet = false;
                  break;
                }
              }
              if (allConditionsMeet) {
                me.logger.debug(["getUsers2: All conditions meet: sord.id={0}, sord.name={1}, user={2}", sord.id, sord.name, userLogString]);
                applicableRules.push(rule);
              }
            } else {
              me.logger.debug(["getUsers2: Add constant user: sord.id={0}, sord.name={1}, user={2}", sord.id, sord.name, userLogString]);
              applicableRules.push(rule);
            }
          }
        }

        for (i = 0; i < applicableRules.length; i++) {
          applicableRule = applicableRules[i];
          userName = me.getUserName(sord, applicableRule);
          if (!userName) {
            me.logger.warn(["getUsers2: Can't determinate user name: {0}", JSON.stringify(applicableRule.user)]);
            continue;
          }

          userEntry = { name: userName, mandatory: applicableRule.mandatory };

          userEntries.push(userEntry);
        }

        me.logger.exit("getUsers2", JSON.stringify(userEntries));

        return userEntries;
      },

      /**
       * Determine if a configuration has a role configured.
       * @param {String|Object} role Role name. If this is defined as an object, a `type` and a `key` has to be defined to load the role from the sords metadata (see {@link sol.common.SordUtils#getValue}). It's also possible to define an object with a `template` property which contains a Handlebars template string.
       * @param {Object[]} rolesConfig Configuration object
       * @param {de.elo.ix.client.Sord} sord (optional) Sord to check conditions from config. If `role` is an object, this has to be defined.
       * @return {String} The name of the role
       */
      retrieveRole: function (role, rolesConfig, sord) {
        var me = this,
            configuredRole = null,
            tplSord;

        me.logger.enter("retrieveRole", role);

        if (role && rolesConfig && (rolesConfig.length > 0)) {
          if (role.type && role.key) {
            if (!sord) {
              throw "IllegalArgumentException: if role is defined by 'type' and 'key', a sord has to be passed.";
            }
            role = sol.common.SordUtils.getValue(sord, role);
          }

          if (role.template) {
            if (!sord) {
              throw "IllegalArgumentException: if role is defined by 'template', a sord has to be passed.";
            }
            tplSord = sol.common.SordUtils.getTemplateSord(sord);
            role = sol.create("sol.common.Template", { source: role.template }).apply(tplSord);
          }

          rolesConfig.some(function (roleConfig) {
            if (roleConfig.name == role) {
              configuredRole = roleConfig.name;
              return true;
            }
          });
        }

        if (!configuredRole) {
          me.logger.debug(["Role '{0}' is not defined in the configuration", role]);
        }

        me.logger.exit("retrieveRole", configuredRole);
        return configuredRole;
      },

      /**
       * @private
       * Returns the username
       * @param {de.elo.ix.client.Sord} sord Sord
       * @param {Object} rule Rule
       * @return {String}
       */
      getUserName: function (sord, rule) {
        var me = this,
            userName, mode;

        userName = rule.user;

        if (sol.common.ObjectUtils.isObject(rule.user)) {
          if (!rule.user.type) {
            throw "User definition type is empty";
          }
          if (!rule.user.key) {
            throw "User definition key is empty";
          }
          userName = sol.common.SordUtils.getValue(sord, rule.user);
        }

        if (!userName) {
          return;
        }

        userName += "";

        mode = rule.mode || rule.user.mode;

        if (mode) {
          switch (mode.toUpperCase()) {
            case "SUPERVISOR":
              userName = me.getSupervisor(rule, userName);
              break;

            default:
              break;
          }
        }

        return userName;
      },

      /**
       * Returns the supervisor of the specified level
       * @param {Object} userDef User definition
       * @param {String} userName User name
       * @return {String} Supervisor
       */
      getSupervisor: function (userDef, userName) {
        var me = this,
            supervisorLevel, supervisors, supervisor;

        userDef = userDef || {};
        userDef.user = userDef.user || {};

        supervisorLevel = userDef.user.supervisorLevel;
        supervisorLevel = (typeof supervisorLevel != "undefined") ? supervisorLevel : userDef.supervisorLevel;
        supervisorLevel = (typeof supervisorLevel != "undefined") ? supervisorLevel : 0;

        supervisors = sol.common.UserUtils.getSupervisorHierarchy(userName);
        if (!supervisors || (supervisors.length <= supervisorLevel)) {
          me.logger.debug(["Can't find supervisor: userConfig={0}, userName={1}, supervisors={2}, supervisorLevel={3}", JSON.stringify(userDef), userName, supervisors, supervisorLevel]);
          return;
        }
        supervisor = supervisors[supervisorLevel];

        me.logger.debug(["getSupervisor: userConfig={0}, userName={1}, supervisors={2}, supervisorLevel={3}, supervisor={4}", JSON.stringify(userDef), userName, supervisors, supervisorLevel, supervisor]);

        return supervisor;
      },

      /**
       * @private
       * Retrieves the users for a role from the configuration array.
       *
       * @param {String} role Lookup string in config object
       * @param {Object[]} config Configuration object
       * @return {Object[]} Array of user configurations
       *
       */
      getUsersByRole: function (role, config) {
        var me = this,
            result;
        me.logger.enter("getUsersByRole", arguments);
        if (config && config.length > 0) {
          config.some(function (roleObj) {
            if (roleObj.name == role) {
              result = roleObj.users;
              return true;
            }
          });
        }
        me.logger.exit("getUsersByRole", result);
        return result;
      },

      /**
       * @private
       * Gets a value from the Sord object.
       *
       * If the condition.val is a number this function tries to retrieve the value as a number.
       *
       * @param {de.elo.ix.client.Sord} sord Sord
       * @param {sol.common.SordMap} sordMap Sord map
       * @param {Object} condition Condition
       * @param {String} condition.type The type were the value should be looked up ("SORD"|"GRP")
       * @param {String} condition.key The lookup key (either an index field name or a sord property)
       * @param {String|Number} condition.val The value to which will be compared, here only used to determine the type (string or number)
       * @return {String|Number}
       */
      getValue: function (sord, sordMap, condition) {
        var value = null;

        switch (condition.type) {
          case "SORD":
            value = (sol.common.SordUtils.isSord(sord) && sord[condition.key]) ? sord[condition.key] : null;
            break;
          case "GRP":
            value = (((typeof condition.val) === "number") || (condition.dataType === "number")) ? sol.common.SordUtils.getObjKeyValueAsNumber(sord, condition.key) : sol.common.SordUtils.getObjKeyValue(sord, condition.key);
            break;
          case "MAP":
            if (condition.dataType === "number") {
              value = sordMap.getNumValue(condition.key);
            } else {
              value = (sordMap.getValue(condition.key) || "") + "";
            }
            break;
          default:
            break;
        }

        return value;
      }
    });