//@include lib_Class.js
//@include lib_sol.common.ObjectUtils.js
//@include lib_sol.common.RepoUtils.js
//@include lib_sol.common.UserUtils.js
//@include lib_sol.common.IxUtils.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();
    }
  });
}

/**
 * Helper for JSON configuration files.
 *
 * The constructor can take either a `load`, a `compose` parameter or a JavaScript object.
 * If the configuration was loaded from the repository (with an additional `writable=true`) as parameter, all changes can be saved back to the repository with {@link #save}.
 * Saving will always be deactivated if the configuration was loaded with compose.
 * If the instance was constructed with an object, it can be saved as a new repository element with {@link #saveNew}.
 *
 * # Load
 * This code loads a configuration in readonly mode from a repository file:
 *
 *     var myconfig = sol.create("sol.common.Config", { load: "(286B8C55-DBF6-2391-8447-479ED57FFDB0)" }).config;
 *
 * The next example also loads a configuration, but also saves changed back:
 *
 *     var cfg = sol.create("sol.common.Config", { load: "ARCPATH:/Administration/Configuration/MyJsonConfig", writable: true });
 *     var myconfig = cfg.config;
 *     // ... make some changes to myconfig ...
 *     cfg.save();
 *
 *
 * # Compose
 * If a new configuration object is created using `compose`,
 * it tries to load the original file as well as a customized file from the same path underneath the 'Business Solutions Costom' folder.
 * Both files will be merged, while the changes in the custom file will override the original settings.
 *
 *     var myconfig = sol.create("sol.common.Config", { compose: "/contract/Configuration/contract.config" }).config;
 *
 * If `compose` is used with an objId, it tries to figure out the relative path inside the Solution folder.
 * If there is none, nothing will be loaded.
 * If there is more then one valid path the first one will be used.
 *
 * # Config
 * To save a new configuration, you could:
 *
 *     var cfg = sol.create("sol.common.Config", { config: { exampleConfigProperty: "a String", anotherProperty: 4711 } });
 *     cfg.saveNew("ARCPATH:/Administration/Configuration/myNewExampleConfig");
 *
 * # Additional comments
 *
 * If used within clients that don't have an ixConnectAdmin-connection configurations are processed by ix function calls.
 * This allows for additional caching among all users and speed up client load times.
 *
 * # Protected config files
 * If a config file contains sensible data (e.g. passwords) it can be secured by adding a `$protected` property (top level).
 * This ensures that no user can obtain a configuration accidentally from cache, without having access to the file itself.
 *
 * @author PZ, ELO Digital Office GmbH
 * @author NM, ELO Digital Office GmbH
 * @version 1.06.000
 *
 * @elojc
 * @eloas
 * @eloix
 * @requires sol.common.ObjectUtils
 * @requires sol.common.RepoUtils
 * @requires sol.common.UserUtils
 * @requires sol.common.IxUtils
 */
sol.define("sol.common.Config", {

  pilcrow: "\u00b6",

  /**
   * @private
   * @property {String} CONFIG_PATH
   */
  CONFIG_PATH: "ARCPATH[(E10E1000-E100-E100-E100-E10E10E10E00)]:/Business Solutions/common/Configuration/base.config",

  /**
   * @private
   * @property {String[]} DEFAULT_BASE_PATHS
   */
  DEFAULT_BASE_PATHS: [
    "Business Solutions",
    "Business Solutions Custom"
  ],

  /**
   * @cfg {String} load (optional) This can be an objId, GUID or arcpath to checkout a JSON configuration file from the repository.
   */

  /**
   * @cfg {String} compose (optional) This has to be a relative arcpath underneath the 'Business Solution' folder or a valid objId
   */

  /**
   * @cfg {Object} config (optional)
   */

  /**
   * @cfg {Boolean} [merge=false] (optional)
   * If `true` the configuration loaded via `load`, will be merged (if possible).
   * This is only neccessary if constructor is invoked with `load`, other wise it will be ignored.
   */

  /**
   * @cfg {Boolean} [forceReload=false] (optional) If `true` cache will be refreshed.
   */

  /**
   * @cfg {Boolean} [copy=false] (optional) If `true`, `config`-property of initialized class will contain a new object instead of a cache reference.
   */

  /**
   * @cfg {Boolean} [writable=false] (optional) If `true`, the loaded configuration is writable via the {@link #save} function
   */

  /**
   * @cfg {Boolean} [exceptionOnBrokenConfig=false] (optional) If `true` the merge function throws an exception if on of the configs (which should be merged) is broken.
   */

  /**
   * @cfg {de.elo.ix.client.IXConnection} connection IX connection
   */

  /**
   * @property {Object} config The loaded (or via constructor initialized) configuration object.
   */

  /**
   * @private
   * @property {String[]} basePaths
   */

  /**
   * @private
   * @property {String} objId Reference to the loaded configuration in the repository
   */

  /**
   * @private
   * @property {sol.Logger} log Internal logger
   */

  /**
   * @private
   * @property {String} eloAsPathPattern
   * Describes the paths where the `loadEloAsConfig` function looks for the ELOas configuration (inside the Solution folder)
   */
  eloAsPathPattern: "/{{SOLUTION}}/Configuration/as.config",

  initialize: function (config) {
    var me = this;

    me.log = sol.create("sol.Logger", { scope: "sol.common.Config" });
    me.merge = config.merge;
    me.copy = config.copy;
    me.forceReload = config.forceReload;
    me.exceptionOnBrokenConfig = config.exceptionOnBrokenConfig;

    me.conn = config.connection || ixConnect;

    me.log.enter("initilialize configuration", config);

    if (config.load && (config.merge !== true)) {
      me.objId = config.load;
      // load remotely if running in clients
      me.isRemote() ? me.reloadRemote(me.forceReload) : me.reload(me.forceReload);
    } else if (config.compose || (config.load && (config.merge === true))) {
      // load remotely if running in clients
      if (me.isRemote()) {
        me.compose = config.compose;
        me.reloadRemote(me.forceReload);
      } else {
        me.compose = me.getCompose(config);
        me.reload(me.forceReload);
      }
    } else if (config.config) {
      me.config = config.config;
      me.objId = config.objId;
    }

    if (config.writable === true && !me.compose) {
      me.writable = true;
    }

    me.config = me.copy && me.rawConfig || me.config;

    me.log.exit("initilialize configuration");
  },

  /**
   * This function identifies if a configuration should be loaded remotely.
   * @private
   * @return {boolean} true if configuration should load remotely
   */
  isRemote: function () {
    return (typeof ixConnectAdmin === "undefined")
      && !!sol.ClassManager.getClass("sol.common.IxUtils");
  },

  /**
   * This function reloads the configuration from the repository, and updates the `config` property remotely. This will allow caching configurations for all users in clients.
   * @param {Boolean} [force=false] (optional) If `true`, cached configs will be ignored.
   */
  reloadRemote: function (force) {
    var me = this,
        cacheKey = me.objId || me.compose, rawCacheKey = cacheKey + "_raw",
        cachedCfg, configObj, rawConfigStr;

    me.log.enter("load configuration remote");

    cachedCfg = sol.common.ConfigCache.get(cacheKey);
    if (!cachedCfg || force === true) {

      if (me.objId) {
        me.log.info(["load configuration remotely: ", me.objId]);
        configObj = sol.common.IxUtils.execute("RF_sol_common_service_GetConfig", {
          objId: me.objId,
          forceReload: force
        }).config;
        rawConfigStr = JSON.stringify(configObj);
      } else if (me.compose) {
        // retrieve merged config using ix interface
        me.log.info(["load merged configuration remotely: ", me.compose]);
        configObj = sol.common.IxUtils.execute("RF_sol_common_service_GetMergedConfig", {
          compose: me.compose,
          forceReload: force
        }).config;
        rawConfigStr = JSON.stringify(configObj);
      }

      sol.common.ConfigCache.put(cacheKey, configObj); // cache reference to object
      me.config = configObj; // give access to reference
      sol.common.ConfigCache.put(rawCacheKey, rawConfigStr); // cache string
      me.rawConfig = me.copy && JSON.parse(rawConfigStr); //  give access to parsed-string -> returns a brand-new object
    } else {
      me.config = cachedCfg;
      me.rawConfig = me.copy && JSON.parse(String(sol.common.ConfigCache.get(rawCacheKey)));
    }

    me.log.exit("load configuration");
  },


  /**
   * This function reloads the configuration from the repository, and updates the `config` property
   * @param {Boolean} [force=false] (optional) If `true`, cached configs will be ignored.
   */
  reload: function (force) {
    var me = this,
        cacheKey = me.objId || me.compose, rawCacheKey = cacheKey + "_raw",
        cachedCfg, configObj, rawConfigStr, mergeHierarchy, mergeObjects;

    me.log.enter("load configuration");

    cachedCfg = sol.common.ConfigCache.getProtected(cacheKey);
    if (!cachedCfg || force === true) {

      if (me.objId) {
        me.log.debug(["load configuration in simple mode (objId={0})", me.objId]);
        try {
          rawConfigStr = sol.common.RepoUtils.downloadToString(me.objId, null, { connection: me.conn });
          configObj = JSON.parse(rawConfigStr);
        } catch (ex) {
          me.log.error("could not parse configuration");
          throw "Configuration error: " + ex;
        }
      } else if (me.compose) {
        mergeHierarchy = me.retrieveMergeHierarchy(true);
        mergeObjects = [];

        me.log.debug(["load configuration in merge mode (compose={0}), mergeing '{1}' configs", me.compose, mergeHierarchy.length]);

        mergeHierarchy.forEach(function (mergeObj) {
          if (mergeObj) {
            try {
              mergeObjects.push(JSON.parse(sol.common.RepoUtils.downloadToString(mergeObj.guid, null, { connection: me.conn })));
            } catch (ex) {
              me.log.warn(["no valid config in '{0}'", mergeObj.guid], ex);
              if (me.exceptionOnBrokenConfig === true) {
                throw "Error loading config file '" + mergeObj.arcPath + "'";
              }
            }
          }
        });

        configObj = sol.common.ObjectUtils.mergeObjects(mergeObjects.shift(), mergeObjects);
        rawConfigStr = JSON.stringify(configObj);
      }

      sol.common.ConfigCache.put(cacheKey, configObj); // cache reference to object
      me.config = configObj; // give access to reference
      sol.common.ConfigCache.put(rawCacheKey, rawConfigStr); // cache string
      me.rawConfig = me.copy && JSON.parse(rawConfigStr); //  give access to parsed-string -> returns a brand-new object
    } else {
      me.config = cachedCfg;
      me.rawConfig = me.copy && JSON.parse(String(sol.common.ConfigCache.get(rawCacheKey)));
    }

    me.log.exit("load configuration");
  },

  /**
   * This function saves changes to the `config` property to the repository
   * @throws Throws an exception, if there is no reference to an repository element (see {@link #objId})
   * @throws Throws an exception, if the oebjct was created in readonly mode (see {@link #writable})
   */
  save: function () {
    var me = this,
        fileContent;

    me.log.enter("save configuration");

    if (!me.writable) {
      throw "Config in readonly mode";
    }

    if (!me.objId) {
      throw "no target path";
    }

    fileContent = new java.lang.String(JSON.stringify(me.config, null, 2));
    sol.common.RepoUtils.uploadSmallContent(me.objId, fileContent);

    me.log.exit("save configuration");
  },

  /**
   * Saves a new configuration file to the repository.
   *
   * - <b>The path has to contain the element name</b>
   * - <b>The path needs to exist (except for the element name), i.e. no new folders will be created</b>
   *
   * @param {String} arcPath An repositoty path.
   * @param {Object} config Configuration
   * @param {de.elo.ix.client.IXConnection} config.connection Index server connection
   */
  saveNew: function (arcPath, config) {
    var me = this,
        conn;

    me.log.enter("save new configuration");

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

    if (!arcPath) {
      throw "Repository path is missing.";
    }
    sol.common.RepoUtils.saveToRepo({ repoPath: arcPath, maskId: CONST.DOC_MASK.GUID_ELOSCRIPTS, contentObject: me.config, connection: conn });

    me.log.exit("save new configuration");
  },

  /**
   * Retrieves all configs (objIds) for a given element which would be merged to it.
   *
   *     cfg.retrieveMergeHierarchy()   // => ["(7AEC9AD9-A487-F472-1C60-24925943A3CB)"]
   *
   *     cfg.retrieveMergeHierarchy(true)   // => [{ guid: "(7AEC9AD9-A487-F472-1C60-24925943A3CB)", basePath: "Business Solutions", arcPath: "ARCPATH:/..." }]
   *
   * @param {boolean} [extended=false] If `true`, this function returns additional infos.
   * @return {String[]|Object[]} Array with GUIDs
   */
  retrieveMergeHierarchy: function (extended) {
    var me = this,
        mergeHierarchy = [],
        composePath;

    me.log.enter("retrieve hierarchy");

    composePath = (me.compose) ? me.compose : me.retrieveComposePath(me.objId);

    if (composePath) {
      me.getBasePaths().forEach(function (basePath) {
        var sep, arcpath, sord;
        try {
          sep = composePath.charAt(0);
          arcpath = "ARCPATH[(E10E1000-E100-E100-E100-E10E10E10E00)]:" + sep + basePath + composePath;
          sord = me.conn.ix().checkoutSord(arcpath, SordC.mbOnlyId, LockC.NO);
          if (extended === true) {
            mergeHierarchy.push({
              guid: sord.guid,
              basePath: basePath,
              arcPath: arcpath
            });
          } else {
            mergeHierarchy.push(sord.guid);
          }
        } catch (ex) {
          mergeHierarchy.push(null);
          me.log.info(["could not determine guid for arcpath='{0}'", arcpath], ex);
        }

      });
    }

    me.log.exit("retrieve hierarchy");

    return mergeHierarchy;
  },

  /**
   * Retrieves the relative path for an object, which can be used to compose.
   * If there is more then one composable path, the first one will be used.
   * @param {String} objId
   * @return {String}
   */
  retrieveComposePath: function (objId) {
    var me = this,
        conn, sordZ, sord, validPath, refPaths;

    conn = (typeof ixConnectAdmin !== "undefined") ? ixConnectAdmin : ixConnect;
    sordZ = new SordZ(SordC.mbMin);
    sordZ.add(SordC.mbRefPaths);
    sord = conn.ix().checkoutSord(objId, sordZ, LockC.NO);

    refPaths = Array.prototype.slice.call(sord.refPaths); // Conversion to JavaScript array for Nashorn compatibility

    refPaths.some(function (refpath) {
      var valid = false;

      if (!refpath.path || refpath.path.length <= 2) {
        return false;
      }

      if (refpath.path[0].guid != "(E10E1000-E100-E100-E100-E10E10E10E00)") {
        return;
      }

      valid = me.getBasePaths().some(function (entry) {
        return entry == refpath.path[1].name;
      });

      if (valid) {
        validPath = refpath;
        return true;
      }
    });

    if (validPath) {
      validPath = Array.prototype.slice.call(validPath.path, 2); // Nashorn compatible call
      validPath = me.pilcrow + validPath.map(function (e) {
        return e.name;
      }).join(me.pilcrow) + me.pilcrow + sord.name;

      me.log.debug(["Compose path for objId={0}: {1}", objId, validPath]);

      return validPath;
    }

    return null;
  },

  /**
   * Checks, if an object is in a valid location, so it can be used for `compose` mode.
   * @param {String} objId (optional) If `undefined` the internal value (from constructor: `load` or `compose`) will be used
   * @return {Boolean}
   */
  validForMergeing: function (objId) {
    var me = this,
        validPath;

    if (!objId && !me.objId) {
      return !!me.compose;
    }

    objId = objId || me.objId;
    validPath = me.retrieveComposePath(objId);

    return (validPath !== null);
  },

  /**
   * Loads the ELOas configuration. The filename has to be `as.config`
   *
   * The `as.config` file content has to be a valid JSON string with at least the following propperties:
   *
   *     {
   *       "protocol": "http",
   *       "server": "myservername",
   *       "port": "8080",
   *       "name": "as-myarchive"
   *     }
   *
   * If a `solution` string is provided, the function tries to lookup a solution specific ELOas configuration.
   * If no specific configuration is found, or no `solution` string is provied, it falls back to the `common` configuration.
   *
   * Lookup path:
   *
   *     "ARCPATH:/Administration/Business Solutions/{{SOLUTION}}/Configuration/as.config"
   *
   * Fallback path:
   *
   *     "ARCPATH:/Administration/Business Solutions/common/Configuration/as.config"
   *
   * The following examle tries to load an ELOas configuration specific for the 'invoice' solution:
   *
   *     var eloAsConfig = sol.create("sol.common.Config").loadEloAsConfig("invoice");
   *
   * The result for the configuration file above:
   *
   *     {
   *       protocol: "http",
   *       server: "myservername",
   *       port: "8080",
   *       name: "as-myarchive"
   *     }
   *
   * This uses the {@link sol.common.ConfigCache}.
   * First it tries to retrieve a configuration for the solution name.
   * If none was found it attempts to read the configuration from a solution specific file.
   * If there is no solution specific ELOas configuration (neither in cache, nor in the archive),
   * it will try to use a cached common configuration or get the one from the archive.
   *
   * @param {String} solution (optional)
   * @return {Object}
   */
  loadEloAsConfig: function (solution) {
    var me = this,
        oldCompose, oldObjId, eloAsConfig, result;

    me.log.enter("load ELOas configuration");

    if (solution) {
      eloAsConfig = sol.common.ConfigCache.getELOasCfg(solution);
      if (!eloAsConfig) {
        try {
          oldCompose = me.compose;
          oldObjId = me.objId;
          me.objId = null;
          me.compose = me.eloAsPathPattern.replace("{{SOLUTION}}", solution);
          me.reload(me.forceReload);
          eloAsConfig = me.config;
          sol.common.ConfigCache.putELOasCfg(solution, eloAsConfig);
        } catch (ex) {
          if (solution == "common") {
            me.log.warn("no ELOas configuration found in 'common'");
          } else {
            me.log.warn(["no solution specific ELOas configuration found for '{0}'", solution]);
          }
        } finally {
          me.objId = oldObjId;
          me.compose = oldCompose;
          me.reload(me.forceReload);
        }
      }
    }

    if (sol.common.ObjectUtils.isEmpty(eloAsConfig) && (solution != "common")) {
      me.log.debug("try fallback to 'common'");
      eloAsConfig = me.loadEloAsConfig("common");
    }

    result = (sol.common.ObjectUtils.isObject(eloAsConfig)) ? eloAsConfig : null;

    me.log.exit("load ELOas configuration");

    return result;
  },

  /**
   * @private
   * Retrieves the base paths from the common.config.baseMergePaths or uses {@link #DEFAULT_BASE_PATHS} as fallback.
   * @return {String}
   */
  getBasePaths: function () {
    var me = this,
        commonCfg;
    if (!me.basePaths) {
      try {
        commonCfg = sol.create("sol.common.Config", { load: me.CONFIG_PATH }).config;
        me.basePaths = (commonCfg && commonCfg.baseMergePaths) ? commonCfg.baseMergePaths : me.DEFAULT_BASE_PATHS;
      } catch (ex) {
        me.basePaths = me.DEFAULT_BASE_PATHS;
      }
    }
    return me.basePaths;
  },

  /**
   * @private
   * Retrieves the compose path from the constructors config object.
   * @param {Object} config
   * @return {String}
   */
  getCompose: function (config) {
    var me = this,
        checkObjId;
    if (config.compose) {
      checkObjId = me.getObjId(config.compose);
      return (checkObjId) ? me.retrieveComposePath(config.compose) : config.compose;
    }
    if (config.load && me.validForMergeing(config.load)) {
      return me.retrieveComposePath(config.load);
    }
    return null;
  },

  /**
   * @private
   * Returns the object ID of a given repository path.
   * @param {String} path Repository path. The path separator is defined by the first character after "ARCPATH:"
   * @return {String} The ID of the new element, or nothing if it does not exist
   */
  getObjId: function (path) {
    var conn, sord;
    if (sol.common.RepoUtils.isObjId(path) || sol.common.RepoUtils.isGuid(path)) {
      return path;
    }
    try {
      conn = (typeof ixConnectAdmin !== "undefined") ? ixConnectAdmin : ixConnect;
      sord = conn.ix().checkoutSord(path, SordC.mbOnlyId, LockC.NO);
      return sord.id;
    } catch (ignore) {
      // Object not found
    }
  }

});


/**
 * Caching for configuration files.
 *
 * Cache is disabled for administrative users. If an administrative user requests a cached config this class always returns `null`.
 *
 * @elojc
 * @eloas
 * @eloix
 *
 * @author PZ, ELO Digital Office GmbH
 * @version 1.03.000
 */
sol.define("sol.common.ConfigCache", {
  singleton: true,
  requires: ["sol.common.Cache"],

  /**
   * @private
   * @property {sol.common.Cache} cache This contains the already loaded configurations
   */
  cache: sol.create("sol.common.Cache"),

  eloAsKeyPrefix: "ELOASCONFIG#",

  /**
   * Retrieves a configuration from the cache.
   * @param {String} key
   * @return {Object}
   */
  get: function (key) {
    var me = this,
        cachedCfg = null;
    if (key === null) {
      throw "Can not access config cache with an 'null' as key";
    }
    if (!me.cacheDisabled() && me.cache.containsKey(key)) {
      me.logger.debug(["cache hit for key={0}", key]);
      cachedCfg = me.cache.get(key);
    }
    return cachedCfg;
  },

  /**
   * Retrieves a configuration from the cache. Configs with protected flag will only delivered from cache for service users.
   * @param {String} key
   * @return {Object}
   */
  getProtected: function (key) {
    var me = this,
        cachedCfg = null;

    cachedCfg = me.get(key);

    return (cachedCfg && !cachedCfg.hasOwnProperty("$protected")) ? cachedCfg : null;
  },

  /**
   * Puts a configuration into the cache.
   * @param {String} key
   * @param {Object} cfg
   */
  put: function (key, cfg) {
    var me = this;
    if (key && cfg) {
      me.logger.debug(["put key={0} into cache", key]);
      me.cache.put(key, cfg);
    }
  },

  /**
   * Retrieves an ELOas configuration from the cache.
   * @param {String} solution
   * @return {Object}
   */
  getELOasCfg: function (solution) {
    var me = this;
    me.logger.debug(["load ELOas '{0}' config from cache", solution]);
    return me.get(me.eloAsKeyPrefix + solution);
  },

  /**
   * Puts an ELOas configuration into the cache.
   * @param {String} solution
   * @param {Object} cfg
   */
  putELOasCfg: function (solution, cfg) {
    var me = this;
    if (solution && cfg) {
      me.logger.debug(["put ELOas '{0}' config into cache", solution]);
      me.cache.put(me.eloAsKeyPrefix + solution, cfg);
    }
  },

  /**
   * @private
   * Checks, if caching is disabled.
   * Currently caching is only disabled for administrative users with interactive login flag.
   * @return {Boolean}
   */
  cacheDisabled: function () {
    var me = this,
        userInfo, isDisabled;
    if (sol.common.UserUtils && sol.common.UserUtils.isMainAdmin && sol.common.UserUtils.isServiceUser) {
      userInfo = ixConnect.loginResult.user;
      isDisabled = sol.common.UserUtils.isMainAdmin(userInfo) && !sol.common.UserUtils.isServiceUser(userInfo);
    }
    if (isDisabled) {
      me.logger.debug("caching disabled for administrative users");
    }
    return isDisabled;
  }

});


/**
 * Utility functions to mix into objects and help working with configurations.
 *
 * @elojc
 * @eloas
 * @eloix
 *
 * @requires sol.common.Config
 * @requires sol.common.ObjectUtils
 * @requires sol.common.StringUtils
 */
sol.define("sol.common.ConfigMixin", {
  singleton: true,
  mixin: true,

  /**
   * Parses the configuration from an Object or a JSON String.
   *
   * Additionally, a JSON file can be specified, to load configuration from.
   *
   *     {
   *       $config: "4711",          // (optional) an objId, GUID or ARCPATH to a JSON file
   *       $property: "configParts.part1",  // (optional) if specified, and the property is an Object, this will be used, instead of the hole JSON file content (can reference sub-objects by using '.' notation)
   *       extraParam: "extra"  // (optional) all properties, without `$` prefix, will be written into the result.config Object (possibly overwriting values from the JSON file)
   *     }
   *
   * If the JSON file (with objId=4711) would contain something like this:
   *
   *     {
   *       "someProperty": "string",
   *       "configParts": {
   *         "part1": {
   *           "firstParam": "first",
   *           "secondParam": "second"
   *         }
   *       }
   *     }
   *
   * The returned Object would look like this:
   *
   *     {
   *       $config: "4711",
   *       $property: "configParts.part1",
   *       config: {
   *         firstParam: "first",
   *         secondParam: "second",
   *         extraParam: "extra"
   *       }
   *     }
   *
   * @param {Object|String} configuration A JavaScript Object, or a JSON String
   * @param {Boolean} allProps
   * @return {Object}
   */
  parseConfiguration: function (configuration, allProps, copy) {
    var me = this,
        configObj, configInstance, config, prop;

    configObj = (sol.common.ObjectUtils.isObject(configuration)) ? configuration : JSON.parse(configuration);

    // load the config from a JSON file
    if (configObj && configObj.$config) {
      configInstance = sol.create("sol.common.Config", { compose: configObj.$config, copy: !!copy });
      if (!configInstance.validForMergeing()) {
        configInstance = sol.create("sol.common.Config", { load: configObj.$config, copy: !!copy }); // config is not in merge hierarchy -> reload in simple mode
      }
      config = configInstance.config;
    }

    // use only the property part of the loaded config
    if (config && configObj.$property) {
      config = me.extractConfigPart(config, configObj.$property);
    }

    if (!config) {
      config = {};
    }

    // copy remaining properties to config (override properties from the file)
    for (prop in configObj) {
      if (configObj.hasOwnProperty(prop) && (!sol.common.StringUtils.startsWith(prop, "$") || allProps)) {
        config[prop] = configObj[prop];
      }
    }

    configObj.config = config;

    return configObj;
  },

  /**
   * @private
   * Extracts a part of a bigger configuration object.
   * @param {Object} config The hole configuration object
   * @param {String} property The property (or path in dot notation), which holds the desired property part
   * @return {Object}
   */
  extractConfigPart: function (config, property) {
    var configPart = property.split(".").reduce(function (obj, key) {
      return obj[key];
    }, config);
    if (sol.common.ObjectUtils.isObject(configPart)) {
      return configPart;
    }
  },

  /**
   * Merge configuration
   * @param {Object|String} configuration A JavaScript Object, or a JSON String
   * @return {Object} merged configuration
   */
  mergeConfiguration: function (configuration) {
    var me = this;
    return me.parseConfiguration(configuration, true).config;
  }
});