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

//@include lib_Class.js
//@include lib_sol.common.Cache.js
//@include lib_sol.common.RepoUtils.js

/*
 * Local definition of the class `sol.common.Cache` for backward compatibility of previous solution packages.
 */
if (!sol.ClassManager.getClass("sol.common.Cache")) {
  sol.define("sol.common.Cache", {

    initialize: function (config) {
      var me = this;
      me.cache = new java.util.concurrent.ConcurrentHashMap(8, 0.9, 1);
    },

    /**
     * Inserts the specified key-value pair into the cache.
     * @param {String} key
     * @param {Object} value
     * @return {Object} The previous value associated with the key, or null if there was no mapping before
     */
    put: function (key, value) {
      var me = this;
      return me.cache.put(key, value);
    },

    /**
     * Inserts all key-value pairs specified by an object into the cache. Existing mappings will be replaced.
     * @param {Object} data Property names will be used as keys and the associated values as values.
     */
    putAll: function (data) {
      var me = this;
      me.cache.putAll(data);
    },

    /**
     * Tests if the specified object is a key in the cache.
     * @param {String} key
     * @return {Boolean}
     */
    containsKey: function (key) {
      var me = this;
      return me.cache.containsKey(key);
    },

    /**
     * Returns the value for the specified key from the cache, or null if the chache contains no mapping for the key.
     * @param {String} key
     * @return {Object}
     */
    get: function (key) {
      var me = this;
      return me.cache.get(key);
    },

    /**
     * Returns an enumeration of all keys in the cache.
     * @return {Object} An `java.util.Enumeration` of all keys
     */
    keys: function () {
      var me = this;
      return me.cache.keys();
    },

    /**
     * Returns a collection view of the values contained in the cache.
     * @return {Object} An `java.util.Collection` of all values
     */
    values: function () {
      var me = this;
      return me.cache.values();
    },

    /**
     * Returns an enumeration of the values in the cache.
     * @return {Object} An `java.util.Enumeration` of all values
     */
    elements: function () {
      var me = this;
      return me.cache.elements();
    },

    /**
     * Removes the key (and its corresponding value) from the cache.
     * @param {String} key
     * @return {Object} The previous value associated with the key, or null if there was no value for the key
     */
    remove: function (key) {
      var me = this;
      return me.cache.remove(key);
    },

    /**
     * Returns the number of key-value pairs in the cache.
     * @return {Number}
     */
    size: function () {
      var me = this;
      return me.cache.size();
    },

    /**
     * Returns `true` if the chache contains no key-value pairs.
     * @return {Boolean}
     */
    isEmpty: function () {
      var me = this;
      return me.cache.isEmpty();
    },

    /**
     * Removes all of the mappings from the cache.
     */
    clear: function () {
      var me = this;
      me.cache.clear();
    }
  });
}

/**
 * User utilities
 *
 * @author MW, ELO Digital Office GmbH
 * @version 1.03.000
 *
 * @elojc
 * @eloas
 * @eloix
 *
 * @requires sol.common.Cache
 * @requires sol.common.JsonUtils
 * @requires sol.common.RepoUtils
 */
sol.define("sol.common.UserUtils", {
  singleton: true,
  requires: ["sol.common.Cache"],

  usersById: null,
  usersByGuid: null,
  usersByName: null,

  /**
   * @private
   * @property {sol.common.Cache} adminUsersById
   * The optimized cache for administrative users.
   * This will be initialized with all administrative users on first access.
   */

  /**
   * @private
   * @property {sol.common.Cache} serviceUsersById
   * The optimized cache for service users.
   * This will be initialized with all service users on first access.
   */

  initialize: function (config) {
    var me = this;
    me.$super("sol.Base", "initialize", [config]);
    me.usersById = sol.create("sol.common.Cache");
    me.usersByGuid = sol.create("sol.common.Cache");
    me.usersByName = sol.create("sol.common.Cache");
  },

  /**
   * Retrieves UserInfo objects.
   * @param {String[]} users The names, GUIDs or IDs of the users
   * @return {de.elo.ix.client.UserInfo[]} The retrieved UserInfo objects
   */
  getUserInfos: function (users) {
    var me = this,
        result;

    if (!users) {
      return;
    }

    me.requireUserInfos(users);

    result = users.map(function (user) {
      return me.getUserInfoFromCache(user);
    });

    return result;
  },

  /**
   * Load user infos into the cache
   * @param {Array} users
   */
  requireUserInfos: function (users) {
    var me = this,
        userInfos, i, userInfo;

    users = users.filter(function (user) {
      userInfo = me.getUserInfoFromCache(user);
      return !userInfo;
    });

    if (users && (users.length > 0)) {
      userInfos = ixConnect.ix().checkoutUsers(users, CheckoutUsersC.BY_IDS, LockC.NO);
      for (i = 0; i < userInfos.length; i++) {
        userInfo = userInfos[i];
        me.writeUserInfoToCache(userInfo);
      }
    }
  },

  /**
   * @private
   * Write user info into the cache
   * @param  {de.elo.ix.client.UserInfo} userInfo
   */
  writeUserInfoToCache: function (userInfo) {
    var me = this,
        osName, userName;

    me.usersById.put(userInfo.id + "", userInfo);
    userName = (userInfo.name + "").toLowerCase();
    me.usersByName.put(userName, userInfo);
    if (userInfo.userProps && (userInfo.userProps.length > UserInfoC.PROP_NAME_OS)) {
      osName = (userInfo.userProps[UserInfoC.PROP_NAME_OS] + "").toLowerCase();
      if (osName) {
        me.usersByName.put(osName, userInfo);
      }
    }
    me.usersByGuid.put(userInfo.guid + "", userInfo);
  },

  /**
   * @private
   * Retrieves the user info from the cache
   * @param {String} user User ID or name
   * @return {de.elo.ix.client.UserInfo} User info
   */
  getUserInfoFromCache: function (user) {
    var me = this,
        userName;

    user += "";

    if (me.isUserId(user)) {
      return me.usersById.get(user);
    } else if (me.isUserGuid(user)) {
      return me.usersByGuid.get(user);
    } else {
      userName = (user + "").toLowerCase();
      return me.usersByName.get(userName);
    }
  },

  /**
   * Checks wether the given string is a user ID
   * @param {String} user User
   * @return {Boolean}
   */
  isUserId: function (user) {
    return /^\d+$/.test(user);
  },

  /**
   * Checks wether the given string is a user GUID
   * @param {String} user User
   * @return {Boolean}
   */
  isUserGuid: function (user) {
    return /^\(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\)$/.test(String(user));
  },

  /**
   * Retrieves an UserInfo object.
   *
   * If the parameter is already an UserInfo, it returns the object itself.
   * Therefore this function can be used to make sure you're dealing with an UserInfo and not just a user name.
   *
   * @param {String|de.elo.ix.client.UserInfo} user The name, GUID or ID of the user (or an UserInfo object)
   * @return {de.elo.ix.client.UserInfo} The UserInfo object, if one was found
   */
  getUserInfo: function (user) {
    var me = this,
        userInfos, _result;
    me.logger.enter("getUserInfo", arguments);
    if (user instanceof UserInfo) {
      return user;
    }
    userInfos = me.getUserInfos([user]);

    _result = (userInfos && userInfos.length > 0) ? userInfos[0] : null;
    me.logger.exit("getUserInfo", _result + "");
    return _result;

    // return (userInfos && userInfos.length > 0) ? userInfos[0] : null;
  },

  /**
   * Returns the supervisior user name
   * @param {String|Number} user User name or ID
   * @return {String} Supervisior user name
   */
  getSupervisor: function (user) {
    var me = this,
        userInfo, userName, supervisorUserInfo;

    userInfo = me.getUserInfo(user);
    if (!userInfo) {
      me.logger.info("User not found: user=" + user);
      return user;
    }

    supervisorUserInfo = me.getUserInfo(userInfo.superiorId);
    userName = supervisorUserInfo.name + "";

    return userName;
  },

  /**
   * Returns the supervisor of the specified level
   * @param {String} userName User name
   * @param {Number} level Level
   * @return {String} Supervisor
   */
  getSupervisorOfLevel: function (userName, level) {
    var me = this,
        supervisors, supervisor;

    if (!userName) {
      me.logger.debug("Can't find supervisor: Username is empty");
      return;
    }

    level = level || 0;

    supervisors = me.getSupervisorHierarchy(userName);
    if (!supervisors || (supervisors.length <= level)) {
      me.logger.debug(["Can't find supervisor: userName={0}, supervisors={1}, level={2}", userName, supervisors, level]);
      return;
    }
    supervisor = supervisors[level];

    return supervisor;
  },

  /**
   * Returns the supervisior hierarchy
   * @param {String|Number} user User name or ID
   * @return {Array} Supervisiors
   */
  getSupervisorHierarchy: function (user) {
    var me = this,
        supervisors = [],
        userInfo, userName, supervisorUserInfo;

    userInfo = me.getUserInfo(user);

    while (true) {

      if (!userInfo || (userInfo.superiorId == userInfo.id) || (supervisors.length > 100)) {
        return supervisors;
      }

      supervisorUserInfo = me.getUserInfo(userInfo.superiorId);
      userName = supervisorUserInfo.name + "";

      supervisors.push(userName);

      userInfo = supervisorUserInfo;
    }
  },

  /**
   * Retrieves an UserInfo object of the current user
   * @return {de.elo.ix.client.UserInfo} The UserInfo object
   */
  getCurrentUserInfo: function () {
    var me = this;

    return me.getUserInfo(ixConnect.userId);
  },

  /**
   * Checks the permissions of the current user
   * @param {Array} flagNames Flags, e.g. "FLAG_IMPORT"
   * @return {Array} missingPermissions
   */
  checkCurrentPermissions: function (flagNames) {
    var missingPermissions = [],
        userInfo, i, flagName, flags, flag;

    if (!flagNames) {
      throw "Flag names are missing";
    }

    userInfo = ixConnect.ix().checkoutUsers([ixConnect.userId + ""], CheckoutUsersC.BY_IDS, LockC.NO)[0];

    for (i = 0; i < flagNames.length; i++) {
      flagName = flagNames[i];
      if (flagName.indexOf("FLAG_") == 0) {
        flags = userInfo.flags;
      } else if (flagName.indexOf("FLAG2_") == 0) {
        flags = userInfo.flags2;
      } else {
        throw "Unknown flag: " + flagName;
      }
      flag = AccessC[flagName];
      if ((flags & flag) == 0) {
        missingPermissions.push(flagName);
      }
    }
    return missingPermissions;
  },

  /**
   * Adds memberships
   *
   *     sol.common.UserUtils.addUsersToGroups(
   *       ['Max Brey', 'Steven Grog'],
   *       ['Accounting']
   *     );
   *
   * @param {Array} userNames Name of the member
   * @param {Array} groupNames Names of the groups
   * @param {Object} params (optional)
   * @param {de.elo.ix.client.IXConnection} params.connection (optional) Index server connection
   */
  addUsersToGroups: function (userNames, groupNames, params) {
    var me = this,
        groupIds = [],
        newUserInfos, groups, group, userInfos, userInfo, membershipIds, groupId, i, j, conn;

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

    params = params || {};
    conn = params.connection || ixConnect;

    newUserInfos = [];
    if (!userNames || (userNames.length === 0) || !groupNames || (groupNames.length === 0)) {
      me.logger.exit("addUsersToGroups");
      return;
    }

    groups = conn.ix().getUserNames(groupNames, CheckoutUsersC.BY_IDS_RAW);
    for (i = 0; i < groups.length; i++) {
      group = groups[i];
      groupIds.push(parseInt(group.id, 10));
    }

    if (groupIds.length === 0) {
      me.logger.exit("addUsersToGroups");
      return;
    }

    userInfos = conn.ix().checkoutUsers(userNames, CheckoutUsersC.BY_IDS_RAW, LockC.NO);

    if (!userInfos && (userInfos.length > 0)) {
      me.logger.exit("addUsersToGroups");
      return;
    }
    for (i = 0; i < userInfos.length; i++) {
      userInfo = new UserInfo(userInfos[i]);

      membershipIds = [];
      for (j = 0; j < userInfo.groupList.length; j++) {
        membershipIds.push(parseInt(userInfo.groupList[j], 10));
      }
      for (j = 0; j < groupIds.length; j++) {
        groupId = groupIds[j];
        if (membershipIds.indexOf(groupId) < 0) {
          membershipIds.push(groupId);
        }
      }
      userInfo.groupList = membershipIds;
      newUserInfos.push(userInfo);
    }
    if (newUserInfos.length > 0) {
      conn.ix().checkinUsers(newUserInfos, CheckinUsersC.WRITE, LockC.NO);
    }
    me.logger.exit("addUsersToGroups");
  },

  /**
   * Remove memberships
   *
   *     sol.common.UserUtils.removeUsersFromGroups(["Everyone"], ["sol.contract.roles.Editors"]);
   *
   * @param {Array} userNames Name of the member
   * @param {Array} groupNames Names of the groups
   */
  removeUsersFromGroups: function (userNames, groupNames) {
    var me = this,
        groupIds = [],
        newUserInfos, groupName, group, userInfos, userInfo, membershipIds, groupId, i, j, index;

    me.logger.enter("removeUsersFromGroups", arguments);
    newUserInfos = [];
    if (!userNames || (userNames.length === 0) || !groupNames || (groupNames.length === 0)) {
      me.logger.exit("removeUsersFromGroups");
      return;
    }

    for (i = 0; i < groupNames.length; i++) {
      groupName = groupNames[i];
      try {
        group = ixConnect.ix().getUserNames([groupName], CheckoutUsersC.BY_IDS_RAW)[0];
        groupIds.push(parseInt(group.id, 10));
      } catch (ex) {
        // ignore
      }
    }

    if (groupIds.length === 0) {
      me.logger.exit("removeUsersFromGroups");
      return;
    }

    userInfos = ixConnect.ix().checkoutUsers(userNames, CheckoutUsersC.BY_IDS_RAW, LockC.NO);

    if (!userInfos && (userInfos.length > 0)) {
      me.logger.exit("removeUsersFromGroups");
      return;
    }
    for (i = 0; i < userInfos.length; i++) {
      userInfo = new UserInfo(userInfos[i]);

      membershipIds = [];
      for (j = 0; j < userInfo.groupList.length; j++) {
        membershipIds.push(parseInt(userInfo.groupList[j], 10));
      }
      for (j = 0; j < groupIds.length; j++) {
        groupId = groupIds[j];
        index = membershipIds.indexOf(groupId);
        if (index > -1) {
          membershipIds.splice(index, 1);
        }
      }
      userInfo.groupList = membershipIds;
      newUserInfos.push(userInfo);
    }
    if (newUserInfos.length > 0) {
      ixConnect.ix().checkinUsers(newUserInfos, CheckinUsersC.WRITE, LockC.NO);
    }
    me.logger.exit("removeUsersFromGroups");
  },

  /**
   * Checks if an user is a main admin.
   *
   * For performance reasons this uses a separate cache for the administrative users.
   * This cache can only be used if the client version is newer then '10.17.010.005' and if this is called with an UserInfo or a Number.
   * In any other cases a fallback will be used and the performance might suffer.
   *
   * @param {de.elo.ix.client.UserInfo|Number|String} user
   * An UserInfo object, an user ID, name or GUID. UserInfo or user ID (Number) should be prefered due to better performance (only relevant for IX client versions above 10.17.010.005).
   * @return {Boolean}
   */
  isMainAdmin: function (user) {
    var me = this,
        userInfo, isAdmin;
    me.logger.enter("isMainAdmin", arguments);

    if (user instanceof UserInfo) {
      // shortcut for performace
      isAdmin = (user.flags >= 0) && ((user.flags & AccessC.FLAG_ADMIN) !== 0);
    } else if (me.useSpecialCaches(user)) {
      // only use cache version, if the version check was successfull and the user parameter is of type Number
      me.initializeSpecialUserCaches();
      isAdmin = !!me.adminUsersById.get((typeof user.id === "number") ? user.id : user);
    } else {
      // Fallback for backwarts compatibility without admin user cache
      userInfo = me.getUserInfo(user);
      isAdmin = (userInfo.flags & AccessC.FLAG_ADMIN) !== 0;
    }

    me.logger.exit("isMainAdmin", isAdmin);
    return isAdmin;
  },

  /**
   * Checks if the given ticket represents an administrative user
   * @param {String} ticket
   * @return {Boolean} True if the user has main admin rights
   */
  isMainAdminTicket: function (ticket) {
    var me = this,
        userInfo, isAdmin;

    if (!ticket) {
      return false;
    }

    me.mainAdminTicketCache = me.mainAdminTicketCache || sol.create("sol.common.Cache");

    ticket += "";
    isAdmin = me.mainAdminTicketCache.get(ticket);

    if (!isAdmin) {
      isAdmin = false;
      try {
        userInfo = me.getUserInfoFromTicket(ticket);
        isAdmin = (userInfo.flags >= 0) && ((userInfo.flags & AccessC.FLAG_ADMIN) !== 0);
        if (isAdmin) {
          me.mainAdminTicketCache.put(ticket, isAdmin);
        }
      } catch (ignore) {
      }
    }

    return isAdmin;
  },

  /**
   * Returns the user ID for a given ticket
   * @param {String} ticket Ticket
   * @return {de.elo.ix.client.UserInfo} User info
   */
  getUserIdFromTicket: function (ticket) {
    var me = this,
        userInfo, userId;

    userInfo = me.getUserInfoFromTicket(ticket);

    if (!userInfo) {
      return;
    }

    userId = userInfo.id;

    return userId;
  },

  /**
   * Returns the UserInfo for a given ticket
   * @param {String} ticket Ticket
   * @return {de.elo.ix.client.UserInfo} User info
   */
  getUserInfoFromTicket: function (ticket) {
    var conn, ticketClientInfo, ticketConnFact, ticketConn, userInfo;

    if (!ticket) {
      return;
    }

    conn = (typeof ixConnectAdmin !== "undefined") ? ixConnectAdmin : ixConnect;

    ticketClientInfo = new ClientInfo();
    ticketClientInfo.ticket = ticket;

    ticketConnFact = sol.common.RepoUtils.createConnFact(conn.connProperties, conn.sessionOptions);
    ticketConn = ticketConnFact.createFromTicket(ticketClientInfo);
    userInfo = ticketConn.loginResult.user;

    ticketConn.close();

    return userInfo;
  },

  /**
   * @since 1.04.000
   * Checks if an user is a service user.
   * That's the case if the user has no interactive login rights and is a main admin.
   *
   * @param {de.elo.ix.client.UserInfo|Number|String} user
   * An UserInfo object, an user ID, name or GUID. UserInfo or user ID (Number) should be prefered due to better performance (only relevant for IX client versions above 10.17.010.005).
   * @return {Boolean}
   */
  isServiceUser: function (user) {
    var me = this,
        userInfo, isServiceUser;
    me.logger.enter("isServiceUser", arguments);

    if (user instanceof UserInfo) {
      // shortcut for performace
      isServiceUser = (user.flags >= 0) && (user.flags2 >= 0) && ((user.flags2 & AccessC.FLAG2_INTERACTIVE_LOGIN) === 0) && ((user.flags & AccessC.FLAG_ADMIN) !== 0);
    } else if (me.useSpecialCaches(user)) {
      // only use cache version, if the version check was successfull and the user parameter is of type Number
      me.initializeSpecialUserCaches();
      isServiceUser = !!me.serviceUsersById.get((typeof user.id === "number") ? user.id : user);
    } else {
      // Fallback for backwarts compatibility without admin user cache
      userInfo = me.getUserInfo(user);
      isServiceUser = ((userInfo.flags2 & AccessC.FLAG2_INTERACTIVE_LOGIN) === 0) && ((userInfo.flags & AccessC.FLAG_ADMIN) !== 0);
    }

    me.logger.exit("isServiceUser", isServiceUser);
    return isServiceUser;
  },

  /**
   * @private
   * Admin caches will only be used if `user` parameter is an UserInfo object or a `Number` and the IX version is above
   * @param {de.elo.ix.client.UserInfo|Number|String} user
   * An UserInfo object, an user ID, name or GUID. UserInfo or user ID (Number) should be prefered due to better performance (only relevant for IX client versions above 10.17.010.005).
   * @return {Boolean}
   */
  useSpecialCaches: function (user) {
    var me = this;
    me.versionCheckAdminCache = me.versionCheckAdminCache || sol.common.RepoUtils.checkVersion(ixConnect.clientVersion, "10.17.010.005"); // cache 'version check' result
    return me.versionCheckAdminCache && (typeof ixConnectAdmin != "undefined") && ((user instanceof UserInfo) || (typeof user === "number"));
  },

  /**
   * @private
   */
  initializeSpecialUserCaches: function () {
    var me = this,
        adminUserCache, serviceUserCache, fui, result, userIterator, userInfo;

    if (!me.specialUserCachesInitialized) {
      adminUserCache = sol.create("sol.common.Cache");
      serviceUserCache = sol.create("sol.common.Cache");

      fui = new FindUserInfo();
      fui.checkoutUsersZ = CheckoutUsersC.ALL_USERS_AND_GROUPS;
      fui.hasFlags = AccessC.FLAG_ADMIN;
      result = ixConnectAdmin.ix().findFirstUsers(fui, 1000);
      if (result.userNames && (result.userNames.size() > 0)) {
        userIterator = result.userNames.values().iterator();
        while (userIterator.hasNext()) {
          userInfo = userIterator.next();
          adminUserCache.put(userInfo.id, userInfo);
          if ((userInfo.flags2 & AccessC.FLAG2_INTERACTIVE_LOGIN) === 0) {
            serviceUserCache.put(userInfo.id, userInfo);
          }
        }
      }

      me.adminUsersById = adminUserCache;
      me.logger.info("'adminUsersById' cache was initialized.");

      me.serviceUsersById = serviceUserCache;
      me.logger.info("'serviceUsersById' cache was initialized.");

      me.specialUserCachesInitialized = true;
    }
  },

  /**
   * Checks, if a user is in a group.
   * @param {String} group Id or name of a group
   * @param {Object} params (optional)
   * @param {Number|String} params.userId (optional) If not set, the userId from `ixConnect` will be used
   * @return {Boolean}
   */
  isInGroup: function (group, params) {
    var me = this,
        isInGroup = false,
        userId, groupsOfUser;

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

    userId = (params && (params.userId || params.userId === 0)) ? params.userId : ixConnect.loginResult.user.id;

    groupsOfUser = ixConnect.ix().getUserNames([userId], CheckoutUsersC.GROUPS_OF_MEMBER_RECURSIVE);

    if (groupsOfUser && (groupsOfUser.length > 0)) {
      isInGroup = groupsOfUser.some(function (groupName) {
        return groupName.id === group || groupName.name.equals(group);
      });
    }

    me.logger.exit("isInGroup");

    return isInGroup;
  },

  /**
   * Returns the email address of an ELO user
   * @param {String|de.elo.ix.client.UserInfo} user The name, GUID or ID of the user (or an UserInfo object)
   * @return {String} Email address
   */
  getMailAddress: function (user) {
    var me = this,
        userInfo, mailAddress;
    me.logger.enter("getMailAddress", arguments);
    if (typeof user == "undefined") {
      throw "user is not defined";
    }
    userInfo = me.getUserInfo(user);
    mailAddress = userInfo.userProps[UserInfoC.PROP_NAME_EMAIL];
    me.logger.exit("getMailAddress", mailAddress + "");
    return mailAddress;
  },

  /**
   * Returns user names
   *
   * Notice: If both flags `onlyUsers` and `onlyGroups` are set an exception will be thrown
   *
   * @param {Object} config Configuration (optional)
   * @param {String} [config.name] (optional) Name If name and namePart is not set then use wildcard search `*`
   * @param {String} [config.namePart] (optional) NamePart Only used if name is not set. If name and namePart is not set use wildcard search `*`
   * @param {String} [config.max=100] (optional) maximum User names
   * @param {Boolean} [config.onlyUsers=false] (optional) If set to `true`, only users will be returned
   * @param {Boolean} [config.onlyGroups=false] (optional) If set to `true`, only groups will be returned.
   * @param {Boolean} [config.visible=false] (optional) If set to `true`, only users with the `visible in userlist` property will be returned
   * @param {Boolean} [config.excludeLockedUsers=false]  (optional) If set to 'true' then the locked users are excluded
   * @return {de.elo.ix.client.UserName[]}
   */
  getUserNames: function (config) {
    var me = this,
        findUserInfo, findResult, userNamesIterator, userNames, index, max, conn, _result;

    me.logger.enter("getUserNames", arguments);
    config = config || {};

    if (config.onlyUsers && config.onlyGroups) {
      // We throw an exception here because it is easier to find a configuration
      // error instead of passing wrong configs to the FindUserInfo Object
      throw Error("Only one flag can be set. Either `onlyUsers` or `onlyGroups`. They were mutually exclusive");
    }

    userNames = [];
    index = 0;
    max = 100;
    conn = (typeof ixConnectAdmin !== "undefined") ? ixConnectAdmin : ixConnect;


    findUserInfo = new FindUserInfo();
    if (config.name) {
      findUserInfo.name = config.name;
    } else if (config.namePart) {
      findUserInfo.name = "*" + config.namePart + "*";
    } else {
      findUserInfo.name = "*";
    }

    if (config.onlyUsers) {
      findUserInfo.onlyUsers = true;
    } else if (config.onlyGroups) {
      findUserInfo.onlyGroups = true;
    }

    if (config.visible === true) {
      findUserInfo.hasFlags2 |= AccessC.FLAG2_VISIBLE_USER;
    }
    if (config.excludeLockedUsers === true) {
      findUserInfo.hasNotFlags |= AccessC.FLAG_NOLOGIN;
    }

    if (config.max) {
      max = config.max;
    }

    findResult = conn.ix().findFirstUsers(findUserInfo, max);
    try {
      while (true) {
        userNamesIterator = findResult.userNames.values().iterator();
        while (userNamesIterator.hasNext()) {
          userNames.push(userNamesIterator.next());
        }
        if (!findResult.isMoreResults()) {
          break;
        }
        if (config.max) {
          break;
        }
        index += findResult.userNames.size();
        findResult = conn.ix().findNextUsers(findResult.searchId, index, max);
      }
    } catch (ex) {
      throw ex;
    } finally {
      conn.ix().findClose(findResult.searchId);
    }

    _result = sol.common.JsonUtils.stringifyAll(userNames) || "";
    _result = _result.split("\",\"").join("\"\n\"");
    me.logger.exit("getUserNames", _result);
    return userNames;
  },

  /**
   * Checks wether a user exists
   * @param {String} userName User name
   * @return {Boolean}
   */
  userExists: function (userName) {
    try {
      ixConnect.ix().getUserNames([userName], CheckoutUsersC.BY_IDS_RAW);
      return true;
    } catch (ex) {
      return false;
    }
  },

  /**
   * Creates an user or group
   * @param {String} userName User name
   * @param {Object} params (optional) Configuration
   * @param {Boolean} [params.checkIfAlreadyExists=true] Don't create the user if it already exists.
   * @param {Number} params.id User ID
   * @param {String} params.guid User GUID
   * @param {String} [params.type=id] ID
   * @param {String} [params.type=user] Type
   * @param {String} params.desc (optional) Description
   * @param {Integer} params.superiorId (optional) Superior ID
   * @param {String} params.windowsUserName (optional) Windows user name
   * @param {String} params.password password (optional) Password
   * @param {Boolean} [params.locked=false] Locked
   * @param {Boolean} [params.visible=true] Visible
   * @param {String} params.email (optional) The E-Mail address of the new user
   * @param {Boolenan} [params.changePassword=true] Change password
   * @param {Boolean} [params.fileAccess=true] File Access
   * @param {Boolean} [params.allowInteractiveLogin=false] Only used if creating a user. User can login.
   * @param {Array} params.permissions (optional) Permissions, e.g. "FLAG_HASFILEACCESS", "FLAG2_SHOW_EXTRA_INFO"
   * @param {Object} params.userProps (optional) to change the "E1", "E2", "E3", "E4", "E5"
   * @return {Number} User ID
   */
  createUser: function (userName, params) {
    var me = this, connection,
        flags = 0,
        flags2 = 0,
        userInfo, userIds, i, permission, permissionParts, firstPermissionPart;

    if (!userName) {
      throw "User name is empty";
    }

    params = params || {};
    params.type = params.type || "user";
    params.checkIfAlreadyExists = (typeof params.checkIfAlreadyExists == "undefined") ? true : params.checkIfAlreadyExists;
    params.locked = (typeof params.locked == "undefined") ? false : params.locked;
    params.visible = (typeof params.visible == "undefined") ? true : params.visible;
    params.allowInteractiveLogin = (params.allowInteractiveLogin === true);
    params.changePassword = (typeof params.changePassword == "undefined") ? true : params.changePassword;
    params.fileAccess = (typeof params.fileAccess == "undefined") ? true : params.fileAccess;
    params.userProps = (typeof params.userProps == "undefined") ? null : params.userProps;

    connection = params.connection || ixConnect;

    if (params.checkIfAlreadyExists) {
      if (me.userExists(userName)) {
        return;
      }
    }
    userInfo = connection.ix().createUser(null);
    userInfo.name = userName;

    if (params.desc) {
      userInfo.desc = params.desc;
    }

    if ((typeof params.id != "undefined") && (params.id != "")) {
      if (!me.userExists(params.id)) {
        userInfo.id = java.lang.Integer.parseInt(params.id);
      }
    }

    if ((typeof params.guid != "undefined") && (params.guid != "")) {
      if (!me.userExists(params.guid)) {
        userInfo.guid = params.guid;
      }
    }

    if (typeof params.superiorId != "undefined") {
      userInfo.superiorId = params.superiorId;
    }

    if (params.type == "user") {
      userInfo.type = UserInfoC.TYPE_USER;
      if (params.allowInteractiveLogin) {
        flags2 |= AccessC.FLAG2_INTERACTIVE_LOGIN;
      }
    } else {
      userInfo.type = UserInfoC.TYPE_GROUP;
    }

    if (params.windowsUserName) {
      userInfo.userProps[UserInfoC.PROP_NAME_OS] = params.windowsUserName;
    }

    if (params.password) {
      userInfo.pwd = params.password;
    }

    if (params.locked) {
      flags |= AccessC.FLAG_NOLOGIN;
    }

    if (params.changePassword) {
      flags |= AccessC.FLAG_CHANGEPW;
    }

    if (params.fileAccess) {
      flags |= AccessC.FLAG_HASFILEACCESS;
    }

    if (params.visible) {
      flags2 |= AccessC.FLAG2_VISIBLE_USER;
    }

    if (params.email) {
      userInfo.userProps[UserInfoC.PROP_NAME_EMAIL] = params.email;
    }

    if (params.permissions) {
      for (i = 0; i < params.permissions.length; i++) {
        permission = params.permissions[i];
        permissionParts = permission.split("_");
        if (permissionParts.length > 1) {
          firstPermissionPart = permissionParts[0];
          if (firstPermissionPart == "FLAG") {
            flags |= AccessC[permission];
          } else if (firstPermissionPart == "FLAG2") {
            flags2 |= AccessC[permission];
          }
        }
      }
    }

    if (params.userProps) {
      if (typeof params.userProps.E1 !== "undefined") {
        userInfo.userProps[4] = params.userProps.E1;
      }
      if (typeof params.userProps.E2 !== "undefined") {
        userInfo.userProps[5] = params.userProps.E2;
      }
      if (typeof params.userProps.E3 !== "undefined") {
        userInfo.userProps[6] = params.userProps.E3;
      }
      if (typeof params.userProps.E4 !== "undefined") {
        userInfo.userProps[7] = params.userProps.E4;
      }
      if (typeof params.userProps.E5 !== "undefined") {
        userInfo.userProps[2] = params.userProps.E5;
      }
    }

    userInfo.flags = flags;
    userInfo.flags2 = flags2;
    userInfo.userProps[UserInfoC.PROP_ACTION] = "";

    userIds = connection.ix().checkinUsers([userInfo], CheckinUsersC.NEW_USER, LockC.NO);
    if (userIds && (userIds.length == 1)) {
      return userIds[0];
    }
  },

  /**
   * Checkout userfolder
   * @param {String|de.elo.ix.client.UserInfo} user The name, GUID or ID of the user (or an UserInfo object)
   * @return {de.elo.ix.client.Sord} sord
   */
  getUserFolder: function (user) {
    var me = this,
        userGuid = me.getUserInfo(user).guid;

    return ixConnect.ix().checkoutSord("OKEY:ELOUSERGUID=" + userGuid, EditInfoC.mbSord, LockC.NO).sord;
  },

  /**
   * Checkout folder username/data
   * @param {String|de.elo.ix.client.UserInfo} user The name, GUID or ID of the user (or an UserInfo object)
   * @return {de.elo.ix.client.Sord} sord
   */
  getFolderUserNameData: function (user) {
    var me = this,
        userGuid = me.getUserInfo(user).guid;

    return ixConnect.ix().checkoutSord("OKEY:ELOINDEX=" + ixConnect.CONST.SORD.ELOINDEX_USER_FOLDER_DATA + userGuid, SordC.mbMin, LockC.NO);
  },

  /**
   * Checkout folder username/data/elo.profile
   * @param {String|de.elo.ix.client.UserInfo} user The name, GUID or ID of the user (or an UserInfo object)
   * @return {de.elo.ix.client.Sord} sord
   */
  getFolderUserNameDataEloProfile: function (user) {
    var me = this,
        userGuid = me.getUserInfo(user).guid;

    return ixConnect.ix().checkoutSord("OKEY:ELOINDEX=" + ixConnect.CONST.SORD.ELOINDEX_USER_FOLDER_DATA_PROFILE + userGuid, SordC.mbMin, LockC.NO);
  },

  /**
   * Checkout folder username/private
   * @param {String|de.elo.ix.client.UserInfo} user The name, GUID or ID of the user (or an UserInfo object)
   * @return {de.elo.ix.client.Sord} sord
   */
  getFolderUserNamePrivate: function (user) {
    var me = this,
        userGuid = me.getUserInfo(user).guid;

    return ixConnect.ix().checkoutSord("OKEY:ELOINDEX=" + ixConnect.CONST.SORD.ELOINDEX_USER_FOLDER_PRIVATE + userGuid, SordC.mbMin, LockC.NO);
  },

  /**
   * Checkout folder username/inbox
   * @param {String|de.elo.ix.client.UserInfo} user The name, GUID or ID of the user (or an UserInfo object)
   * @return {de.elo.ix.client.Sord} sord
   */
  getFolderUserNameInbox: function (user) {
    var me = this,
        userGuid = me.getUserInfo(user).guid;

    return ixConnect.ix().checkoutSord("OKEY:ELOINDEX=" + ixConnect.CONST.SORD.ELOINDEX_USER_FOLDER_INBOX + userGuid, SordC.mbMin, LockC.NO);
  }
});