# ConfigItem.java

    /*
    ** Module   : ConfigItem.java
    ** Abstract : Core class for resolving clientConfig and webClient configs.
    **
    ** Copyright (c) 2024-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --------------------------------------Description-----------------------------------------
    ** 001 GBB 20240624 Created initial version.
    ** 002 GBB 20240718 read method with all args made public.
    ** 003 CA  20240722 Fixed reading of Map configurations.  Changed the 'clientConfig/properties' to be a real
    **                  map.
    ** 004 GBB 20241010 Removing config VALIDATE_SEC_CERT (security:certificate:validate). Adding convenience
    **                  overloading method read that accepts DirectoryService, scope and accounts.
    ** 005 AP  20250303 Added timeout for spawner launching.
    ** 006 GBB 20250403 Added SOCKET_TRUSTSTORE_FILE, SOCKET_TRUSTSTORE_PASS.
    **                  cfgOverrides resolvement for the security context by explicit call to resolveCfgOverrides.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import com.goldencode.p2j.directory.*;
    import com.goldencode.p2j.main.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.logging.*;

    import java.util.*;
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.*;
    import java.util.logging.*;

    /**
     * Core class for resolving clientConfig and webClient configs.
     *
     * @param   <T>
     *          The type for the value of the config.
     */
    public final class ConfigItem<T>
    {
       /**
        * Stores global data relating to the state of the current context.
        */
       private static class WorkArea
       {
          /** Flag to indicate if the config overrides are resolved for this security context. */
          private final AtomicBoolean areCfgOverridesInitialized = new AtomicBoolean(false);

          /** The config overrides resolved for this security context. */
          private final Map<String, String> cfgOverrides = new ConcurrentHashMap<>();

          /**
           * Constructor.
           */
          WorkArea()
          {
          }
       }

       /** Context-local global data area for the config items. */
       private static final ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
       {
          /**
           * Initializes the work area, the first time it is requested within a
           * new context.
           *
           * @return   The newly instantiated work area.
           */
          protected synchronized WorkArea initialValue()
          {
             return new WorkArea();
          }
       };

       /**
        * Resolve the config overrides for the specified accounts and directory scope and store them in the
        * context local.
        *
        * @param   dirScope
        *          The directory scope.
        * @param   accountIds
        *          The account ids for the directory lookup.
        */
       public static void resolveCfgOverrides(Utils.DirScope dirScope, String[] accountIds)
       {
          Map<String, String> cfgOverrides = local.get().cfgOverrides;
          cfgOverrides.clear();

          String cfgOverridesString = ConfigItem.CFG_OVERRIDES.read(null, dirScope, accountIds);
          if (cfgOverridesString != null)
          {
             for (String cfgOverride : cfgOverridesString.split(" "))
             {
                String[] configNameValue = cfgOverride.trim().split("=");
                cfgOverrides.put(configNameValue[0].trim(),
                                 configNameValue.length > 1 ? configNameValue[1].trim() : null);
             }
          }
          local.get().areCfgOverridesInitialized.set(true);
       }

       /** The type of client config, based on the origin. */
       public enum Type
       {
          /** Type for config originally in clientConfig. */
          CLIENT_CONFIG,

          /** Type for config originally in webClient. */
          WEB_CLIENT,

          /** Type for config coming from cmd line or cfgOverrides. */
          CFG_OVERRIDES,

          /** Type for config originating runtime. */
          RUNTIME,

          /** Type for config declared directly in server or account containers */
          SERVER_ACCOUNT
       }

       /* -----------------------------------
          Original clientConfig/*
          ----------------------------------- */

       public static final ConfigItem<String> CFG_OVERRIDES = new ConfigItem<>(String.class, "cfgOverrides");

       public static final ConfigItem<Boolean> SECURE =
          new ConfigItem<>(Boolean.class, "secure", "net", "connection", "secure", Type.CLIENT_CONFIG);

       public static final ConfigItem<String> COMMAND = new ConfigItem<>(String.class, "command");

       public static final ConfigItem<String> CLASSPATH = new ConfigItem<>(String.class, "classpath");

       public static final ConfigItem<String> LIB_PATH = new ConfigItem<>(String.class, "libPath");

       public static final ConfigItem<String> SPAWNER = new ConfigItem<>(String.class, "spawner");

       /**
        * Enables spawned client process logging by the native process. Catches JVM errors. Used only for
        * debugging.
        */
       public static final ConfigItem<Boolean> SPAWNER_DEBUG = new ConfigItem<>(Boolean.class, "enableSpawnerDebug");

       public static final ConfigItem<String> WORKING_DIR = new ConfigItem<>(String.class, "workingDir");

       public static final ConfigItem<String> OUTPUT_TO_FILE =
          new ConfigItem<>(String.class, "outputToFile", "server", "clientConfig", "outputToFile", Type.CLIENT_CONFIG);

       public static final ConfigItem<String> CONFIG_FILE = new ConfigItem<>(String.class, "configFile");

       public static final ConfigItem<String> CLASSPATH_EXTRA_JARS =
          new ConfigItem<>(String.class, "classpath-extra-jars");

       public static final ConfigItem<String> JVM_ARGS = new ConfigItem<>(String.class, "jvmArgs");

       public static final ConfigItem<Integer> MIN_AGENT_PORT = new ConfigItem<>(Integer.class, "minAgentPort");

       public static final ConfigItem<Integer> MAX_AGENT_PORT = new ConfigItem<>(Integer.class, "maxAgentPort");

       /** OS user for processes appserver / scheduled batch. */
       public static final ConfigItem<String> SYSTEM_USER = new ConfigItem<>(String.class, "systemUser");

       /** OS pass for processes appserver / scheduled batch. */
       public static final ConfigItem<String> SYSTEM_PASS = new ConfigItem<>(String.class, "systemPassword");

       /** The relative desktop node path */
       public static final ConfigItem<String> KEYBOARD_LAYOUTS = new ConfigItem<>(String.class, "keyboardLayouts");

       public static final ConfigItem<String> KEYBOARD_LAYOUT_ID =
          new ConfigItem<>(String.class, "defaultKeyboardLayoutId");

       public static final ConfigItem<Boolean> KEYBOARD_SHORTCUTS_UNDERLINE =
          new ConfigItem<>(Boolean.class, "underline-keyboard-shortcuts");

       public static final ConfigItem<Map> HELPER_APPS = new ConfigItem<>(Map.class, "helper-applications");

       public static final ConfigItem<Map> ENV_PROPERTIES = new ConfigItem<>(Map.class, "properties");

       /** Launch timeout for the spawner, expressed in seconds */
       public static final ConfigItem<Integer> SPAWNER_LAUNCH_TIMEOUT =
          new ConfigItem<>(Integer.class,
                          "spawnerLaunchTimeout",
                          "remote",
                          "spawner",
                          "launchTimeout",
                          Type.CLIENT_CONFIG);

       /* -----------------------------------
          Original webClient/*
          ----------------------------------- */

       public static final ConfigItem<String> PROJECT_TOKEN =
          new ConfigItem<>(String.class, "project_token", "client", "project", "token", Type.WEB_CLIENT);

       public static final ConfigItem<String> CFG_OVERRIDES_WEB =
          new ConfigItem<>(String.class, "cfgOverrides", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> EMBEDDED =
          new ConfigItem<>(Boolean.class, "embedded", "client", "web", "embedded", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> VIRTUAL_DESKTOP =
          new ConfigItem<>(Boolean.class, "virtualDesktop", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> DEFAULT_OS_USER =
          new ConfigItem<>(String.class, "defaultOsUser", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> OVERRIDE_OS_USER =
          new ConfigItem<>(Boolean.class, "override", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> GUI_IDENTIFIER =
          new ConfigItem<>(String.class, "guiIdentifier", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> CHUI_IDENTIFIER =
          new ConfigItem<>(String.class, "chuiIdentifier", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> EMBEDDED_IDENTIFIER =
          new ConfigItem<>(String.class, "embeddedIdentifier", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> LOGIN_TITLE =
          new ConfigItem<>(String.class, "loginPageTitle", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> LOGOUT_TITLE =
          new ConfigItem<>(String.class, "logoutPageTitle", "", "", "", Type.WEB_CLIENT);

       /** The port for the web server on the client process. */
       public static final ConfigItem<Integer> PORT =
          new ConfigItem<>(Integer.class, "port", "client", "web", "port", Type.WEB_CLIENT);

       /** The host for the web server on the client process. */
       public static final ConfigItem<String> HOST =
          new ConfigItem<>(String.class, "host", "client", "web", "host", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> POLL_TIMEOUT =
          new ConfigItem<>(Integer.class, "pollTimeout", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> CONNECTION_TEST_TIMEOUT =
          new ConfigItem<>(Integer.class, "connectionTestTimeout", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> CLIENT_PORT_RANGE_FROM =
          new ConfigItem<>(Integer.class, "portsRange/from", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> CLIENT_PORT_RANGE_TO =
          new ConfigItem<>(Integer.class, "portsRange/to", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> PROXY_HOST =
          new ConfigItem<>(String.class,
                           "portsRange/forwardedHost",
                           "client",
                           "web",
                           "forwardedHost",
                           Type.WEB_CLIENT);

       public static final ConfigItem<String> PROXY_PROTOCOL =
          new ConfigItem<>(String.class,
                           "portsRange/forwardedProto",
                           "client",
                           "web",
                           "forwardedProto",
                           Type.WEB_CLIENT);

       public static final ConfigItem<String> PROXY_CLIENT_NAME_PREFIX =
          new ConfigItem<>(String.class, "portsRange/namePrefix", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> PROXY_CLIENT_PATH_SEGMENT =
          new ConfigItem<>(String.class, "proxyPathSegment", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> PROXY_SERVER_PATH =
          new ConfigItem<>(String.class, "serverProxyPath", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> PROXY_REMOVE_SERVER_PATH =
          new ConfigItem<>(Boolean.class, "removeProxyPathInWebPages", "", "", "", Type.WEB_CLIENT);

       /** The "watchdogTimeout" parameter to define the watch dog timeout */
       public static final ConfigItem<Integer> WATCHDOG_TIMEOUT =
          new ConfigItem<>(Integer.class, "watchdogTimeout", "client", "web", "watchdogTimeout", Type.WEB_CLIENT);

       /** The delay between successful MSG_PING_PONG pairs if the server replies with MSG_PING_PONG */
       public static final ConfigItem<Integer> PING_PONG_INTERVAL =
          new ConfigItem<>(Integer.class, "pingPongInterval", "client", "web", "pingPongInterval", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> MAX_LOST_PINGS =
          new ConfigItem<>(Integer.class, "maxLostPings", "client", "web", "maxLostPings", Type.WEB_CLIENT);

       /** The idle time after which the connection should be closed */
       public static final ConfigItem<Integer> MAX_IDLE_TIME =
          new ConfigItem<>(Integer.class, "maxIdleTime", "client", "web", "maxIdleTime", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> MAX_HTTP_IDLE_TIME =
          new ConfigItem<>(Integer.class, "embWebServerMaxIdleTimeout", "client", "web", "maxHttpIdleTimeout", Type.WEB_CLIENT);

       /** The "webSocketTimeout" parameter to define the maximal idle time of the configured websocket */
       public static final ConfigItem<Integer> WEB_SOCKET_TIMEOUT =
          new ConfigItem<>(Integer.class, "webSocketTimeout", "client", "web", "socketTimeout", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> CONNECT_RETRY_DELAY =
          new ConfigItem<>(Integer.class,
                           "delayBetweenTriesToConnect",
                           "client",
                           "web",
                           "delayBetweenTriesToConnect",
                           Type.WEB_CLIENT);

       public static final ConfigItem<Integer> PING_DELAY =
          new ConfigItem<>(Integer.class, "delayBetweenPingTries", "client", "web", "delayBetweenPingTries", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> MAX_BINARY_MSG_SIZE =
          new ConfigItem<>(Integer.class, "maxBinaryMessage", "client", "web", "maxBinaryMessage", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> MAX_TEXT_MSG_SIZE =
          new ConfigItem<>(Integer.class, "maxTextMessage", "client", "web", "maxTextMessage", Type.WEB_CLIENT);

       public static final ConfigItem<String> TRY_CONNECT_MSG =
          new ConfigItem<>(String.class, "tryToConnectMessage", "client", "web", "tryToConnectMessage", Type.WEB_CLIENT);

       public static final ConfigItem<String> SERVER_UNAVAILABLE_MSG =
          new ConfigItem<>(String.class, "serverUnavailableMessage", "client", "web", "serverUnavailableMessage", Type.WEB_CLIENT);

       public static final ConfigItem<String> CONNECTION_RESTORED_MSG =
          new ConfigItem<>(String.class,
                           "connectionRestoredMessage",
                           "client",
                           "web",
                           "connectionRestoredMessage",
                           Type.WEB_CLIENT);

       public static final ConfigItem<Integer> MAX_OUT_BUFFER_SIZE =
          new ConfigItem<>(Integer.class, "maxOutputBufferSize", "client", "web", "maxOutputBufferSize", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> MAX_OUT_AGGREGATION_SIZE = new ConfigItem<>(Integer.class,
                                                                                           "maxOutputAggregationSize", "client", "web", "maxOutputAggregationSize", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> MAX_RESPONSE_HEADER_SIZE =
          new ConfigItem<>(Integer.class, "maxResponseHeaderSize", "client", "web", "maxResponseHeaderSize", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> MAX_REQUEST_HEADER_SIZE =
          new ConfigItem<>(Integer.class, "maxRequestHeaderSize", "client", "web", "maxRequestHeaderSize", Type.WEB_CLIENT);

       public static final ConfigItem<String> RENDERER =
          new ConfigItem<>(String.class, "renderer", "client", "gui", "renderer", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> GRAPHICS_CACHED =
          new ConfigItem<>(Boolean.class, "graphicsCached", "client", "gui", "graphicsCached", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> DISABLE_PIXEL_MANIPULATION =
          new ConfigItem<>(Boolean.class,
                           "disablePixelManipulation",
                           "client",
                           "gui",
                           "disablePixelManipulation",
                           Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> TASKBAR =
          new ConfigItem<>(Boolean.class, "taskbar", "client", "gui", "taskbar", Type.WEB_CLIENT);

       public static final ConfigItem<String> TASKBAR_STYLE =
          new ConfigItem<>(String.class, "taskBarStyle", "client", "gui", "taskBarStyle", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> CLIPBOARD =
          new ConfigItem<>(Boolean.class, "clipboard", "client", "web", "clipboard", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> ROWS =
          new ConfigItem<>(Integer.class, "rows", "client", "chui", "rows", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> COLS =
          new ConfigItem<>(Integer.class, "columns", "client", "chui", "columns", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> DESKTOP_HEIGHT =
          new ConfigItem<>(Integer.class, "desktopHeight", "client", "gui", "desktopHeight", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> DESKTOP_WIDTH =
          new ConfigItem<>(Integer.class, "desktopWidth", "client", "gui", "desktopWidth", Type.WEB_CLIENT);

       public static final ConfigItem<String> BACKGROUND =
          new ConfigItem<>(String.class, "background", "client", "chui", "background", Type.WEB_CLIENT);

       public static final ConfigItem<String> FOREGROUND =
          new ConfigItem<>(String.class, "foreground", "client", "chui", "foreground", Type.WEB_CLIENT);

       public static final ConfigItem<String> SELECTION =
          new ConfigItem<>(String.class, "selection", "client", "chui", "selection", Type.WEB_CLIENT);

       public static final ConfigItem<String> FONT_NAME =
          new ConfigItem<>(String.class, "fontname", "client", "chui", "fontname", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> FONT_SIZE =
          new ConfigItem<>(Integer.class, "fontsize", "client", "chui", "fontsize", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> FORCE_CENTERED_DIALOG =
          new ConfigItem<>(Boolean.class, "force-dialogs-in-center", "", "", "", Type.WEB_CLIENT);

       /** Indicates whether or not pause x no message should enable the default-window */
       public static final ConfigItem<Boolean> BYPASS_NO_MSG_WIN_ON_PAUSE =
          new ConfigItem<>(Boolean.class, "bypass-default-window-show-in-pause-no-message", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> USE_LOCALSTORAGE =
          new ConfigItem<>(Boolean.class,
                           "useLocalStorage",
                           "client",
                           "web",
                           "useLocalStorage",
                           Type.WEB_CLIENT);

       public static final ConfigItem<Integer> BROADCAST_CHANNEL_PING_TIMEOUT =
          new ConfigItem<>(Integer.class,
                           "broadcastChannelPingTimeout",
                           "client",
                           "web",
                           "broadcastChannelPingTimeout",
                           Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> ONE_BROWSER_POLICY =
          new ConfigItem<>(Boolean.class, "oneBrowserPolicy", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> UPLOAD_SINGLE_FILE =
          new ConfigItem<>(String.class, "uploadSingleFile", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> UPLOAD_TOTAL_FILES =
          new ConfigItem<>(String.class, "uploadTotalFiles", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> MAX_FILE_SIZE_ON_DISK =
          new ConfigItem<>(String.class, "fileSizeThresholdForDiskWriting", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> SPREADSHEET_ENABLED =
          new ConfigItem<>(Boolean.class, "spreadsheet/enabled", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Integer> BROWSER_LOAD_TIMEOUT =
          new ConfigItem<>(Integer.class, "htmlBrowser/load-timeout", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> BROWSER_CONTEXT_PATH =
          new ConfigItem<>(String.class, "htmlBrowser/default-context/context-path", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<String> BROWSER_RESOURCE_BASE =
          new ConfigItem<>(String.class, "htmlBrowser/default-context/resource-base", "", "", "", Type.WEB_CLIENT);

       /** The placeholder is to be replaced by the name. */
       public static final ConfigItem<String> BROWSER_WHITELIST_CONTEXT_PATH =
          new ConfigItem<>(String.class, "htmlBrowser/whitelist/%s/context-path", "", "", "", Type.WEB_CLIENT);

       /** The placeholder is to be replaced by the name. */
       public static final ConfigItem<String> BROWSER_WHITELIST_RESOURCE_BASE =
          new ConfigItem<>(String.class, "htmlBrowser/whitelist/%s/resource-base", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<List> LOGIN_PARAMS =
          new ConfigItem<>(List.class, "loginParams", "", "", "", Type.WEB_CLIENT);

       /** The placeholder is to be replaced by the name of the login param. */
       public static final ConfigItem<String> LOGIN_PARAMS_REGEX =
          new ConfigItem<>(String.class, "loginParamsRegex/%s", "", "", "", Type.WEB_CLIENT);

       public static final ConfigItem<Boolean> CHANGE_EXPIRING_OS_PASS =
          new ConfigItem<>(Boolean.class, "expiringOsPassChange", "", "", "", Type.WEB_CLIENT);

       /* -----------------------------------
          Configs declared directly in server or account containers
          -------------------------------------- */

       public static final ConfigItem<Integer> TEMP_CLIENT_POOL_SIZE =
          new ConfigItem<>(Integer.class, "tempClient/poolSize", "", "", "", Type.SERVER_ACCOUNT);

       public static final ConfigItem<Integer> TEMP_CLIENT_TIMEOUT =
          new ConfigItem<>(Integer.class, "tempClient/timeout", "", "", "", Type.SERVER_ACCOUNT);

       /* -----------------------------------
          runtime / cfgOverrides / other root nodes,
          not resolved from CLIENT_CONF_CONTAINER and WEB_CLIENT_CONTAINER,
          not always passed as client options
          -------------------------------------- */

       /** The server's secure port. */
       public static final ConfigItem<Integer> SERVER_SECURE_PORT =
          new ConfigItem<>(Integer.class, "securePort", "net", "server", "secure_port", Type.CFG_OVERRIDES);

       public static final ConfigItem<Integer> SERVER_INSECURE_PORT =
          new ConfigItem<>(Integer.class, "", "net", "server", "insecure_port", Type.CFG_OVERRIDES);

       public static final ConfigItem<Integer> SERVER_PORT =
          new ConfigItem<>(Integer.class, "", "net", "server", "port", Type.CFG_OVERRIDES);

       public static final ConfigItem<String> SERVER_HOST =
          new ConfigItem<>(String.class, "", "net", "server", "host", Type.CFG_OVERRIDES);

       public static final ConfigItem<Boolean> SERVER_SOCKET_NIO =
          new ConfigItem<>(Boolean.class, "", "net", "socket", "nio", Type.CFG_OVERRIDES);

       public static final ConfigItem<Boolean> SERVER_SSL_TRACKSEQNO =
          new ConfigItem<>(Boolean.class, "", "net", "ssl", "trackSeqNo", Type.CFG_OVERRIDES);

       public static final ConfigItem<String> SOCKET_TRUSTSTORE_FILE =
          new ConfigItem<>(String.class, "", "ssl-socket", "truststore", "location", Type.CFG_OVERRIDES);

       public static final ConfigItem<String> SOCKET_TRUSTSTORE_PASS =
          new ConfigItem<>(String.class, "", "ssl-socket", "truststore", "password", Type.CFG_OVERRIDES);

       public static final ConfigItem<String> LANG =
          new ConfigItem<>(String.class, "lang", "client", "web", "lang", Type.RUNTIME);

       public static final ConfigItem<Integer> CACHE_SIZE = new ConfigItem<>(Integer.class,
                                                                             "client-cache-size",
                                                                             "client",
                                                                             "text-metrics",
                                                                             "cache-size",
                                                                             Type.RUNTIME);
       public static final ConfigItem<String> DRIVER_TYPE =
          new ConfigItem<>(String.class, "", "client", "driver", "type", Type.RUNTIME);

       public static final ConfigItem<String> WEB_ROOT =
          new ConfigItem<>(String.class, "webRoot", "client", "web", "webRoot", Type.RUNTIME);

       public static final ConfigItem<String> LOGOUT_PAGE =
          new ConfigItem<>(String.class, "logoutPage", "web", "url", "logoutPage", Type.RUNTIME);

       public static final ConfigItem<String> LOGIN_PAGE =
          new ConfigItem<>(String.class, "loginPage", "web", "url", "loginPage", Type.RUNTIME);

       public static final ConfigItem<String> SERVER_BASE_URL =
          new ConfigItem<>(String.class, "serverBaseUrl", "web", "url", "serverBaseUrl", Type.RUNTIME);

       public static final ConfigItem<String> SSO_SUBJECT_ID =
          new ConfigItem<>(String.class, "", "sso", "subject", "id", Type.RUNTIME);

       public static final ConfigItem<String> SSO_STORAGE_ID =
          new ConfigItem<>(String.class, "", "sso", "storage", "id", Type.RUNTIME);

       public static final ConfigItem<String> SPAWNER_UUID =
          new ConfigItem<>(String.class, "", "server", "spawner", "uuid", Type.RUNTIME);

       /** Enables web client debug logging in js. */
       public static final ConfigItem<Boolean> JS_DEBUG_LOGGING = new ConfigItem<>(Boolean.class,
                                                                                   "enableDebugLogging",
                                                                                   "client",
                                                                                   "web",
                                                                                   "enableDebugLogging",
                                                                                   Type.CFG_OVERRIDES);

       /** Logger. */
       private static final CentralLogger LOG = CentralLogger.get(ConfigItem.class);

       /** The directory container name for generic client configs. */
       private static final String CLIENT_CONF_CONTAINER = "clientConfig/";

       /** The directory container name for web client configs. */
       private static final String WEB_CLIENT_CONTAINER = "webClient/";

       /** The class for the config value. */
       private final Class<T> valueClass;

       /** The directory node name for the config. */
       private final String nodeName;

       /** The {@link Type} for the config. */
       private final Type type;

       /** The bootstrap option full name: category:group:key. */
       private final String optionName;

       /** The bootstrap option category. */
       private final String optionCategoryName;

       /** The bootstrap option group. */
       private final String optionGroupName;

       /** The bootstrap option key. */
       private final String optionKeyName;

       /**
        * Private constructor.
        *
        * @param   valueClass
        *          The class for the config value.
        * @param   nodeName
        *          The directory node name for the config.
        */
       private ConfigItem(Class<T> valueClass, String nodeName)
       {
          this(valueClass, nodeName, null, null, null, Type.CLIENT_CONFIG);
       }

       /**
        * Private constructor.
        *
        * @param   valueClass
        *          The class for the config value.
        * @param   nodeName
        *          The directory node name for the config.
        * @param   optionCategoryName
        *          The bootstrap option category.
        * @param   optionGroupName
        *          The bootstrap option group.
        * @param   optionKeyName
        *          The bootstrap option key.
        * @param   type
        *          The {@link Type} for the config.
        */
       private ConfigItem(Class<T> valueClass,
                          String nodeName,
                          String optionCategoryName,
                          String optionGroupName,
                          String optionKeyName,
                          Type type)
       {
          this.valueClass = valueClass;
          this.nodeName = nodeName;
          this.optionCategoryName = optionCategoryName;
          this.optionGroupName = optionGroupName;
          this.optionKeyName = optionKeyName;
          this.optionName = (optionCategoryName == null && optionGroupName == null && optionKeyName == null) ?
             null :
             optionCategoryName + ":" + optionGroupName + ":" + optionKeyName;
          this.type = type;
       }

       /**
        * Returns the string representation of the client config. The same as {@link #name}.
        *
        * @return  See above.
        */
       @Override
       public String toString()
       {
          return nodeName;
       }

       /**
        * Getter for {@link #name}.
        *
        * @return  See above.
        */
       public String name()
       {
          return nodeName;
       }

       /**
        * Getter for {@link #optionCategoryName}.
        *
        * @return  See above.
        */
       public String category()
       {
          return optionCategoryName;
       }

       /**
        * Getter for {@link #optionGroupName}.
        *
        * @return  See above.
        */
       public String group()
       {
          return optionGroupName;
       }

       /**
        * Getter for {@link #optionKeyName}.
        *
        * @return  See above.
        */
       public String key()
       {
          return optionKeyName;
       }

       /**
        * Getter for {@link #optionName}.
        *
        * @return  See above.
        */
       public String fullOption()
       {
          return optionName;
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        *
        * @return  The client config value as read from directory.
        */
       public T read(T defaultValue)
       {
          return read(null, null, defaultValue, null, null, null);
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   placeholderValue
        *          The value to replace the placeholder in the directory node name for the config.
        *
        * @return  The client config value as read from directory.
        */
       public T read(T defaultValue, String placeholderValue)
       {
          return read(null, null, defaultValue, null, placeholderValue, null);
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   placeholderValue
        *          The value to replace the placeholder in the directory node name for the config.
        * @param   ds
        *          The directory service.
        *
        * @return  The client config value as read from directory.
        */
       public T read(T defaultValue, String placeholderValue, DirectoryService ds)
       {
          return read(null, null, defaultValue, ds, placeholderValue, null);
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   webDriverType
        *          The web driver type.
        *
        * @return  The client config value as read from directory.
        */
       public T read(T defaultValue, WebDriverType webDriverType)
       {
          return read(webDriverType, null, defaultValue, null, null, null);
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   webDriverType
        *          The web driver type.
        * @param   placeholderValue
        *          The value to replace the placeholder in the directory node name for the config.
        *
        * @return  The client config value as read from directory.
        */
       public T read(T defaultValue, WebDriverType webDriverType, String placeholderValue)
       {
          return read(webDriverType, null, defaultValue, null, placeholderValue, null);
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   scope
        *          The directory scope.
        *
        * @return  The client config value as read from directory.
        */
       public T read(T defaultValue, Utils.DirScope scope)
       {
          return read(null, scope, defaultValue, null, null, null);
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   scope
        *          The directory scope.
        * @param   accountIds
        *          The account ids for the directory lookup.
        *
        * @return  The client config value as read from directory.
        */
       public T read(T defaultValue, Utils.DirScope scope, String[] accountIds)
       {
          return read(null, scope, defaultValue, null, null, accountIds);
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   ds
        *          The directory service.
        * @param   scope
        *          The directory scope.
        * @param   accountIds
        *          The account ids for the directory lookup.
        *
        * @return  The client config value as read from directory.
        */
       public T read(T defaultValue, DirectoryService ds, Utils.DirScope scope, String[] accountIds)
       {
          return read(null, scope, defaultValue, ds, null, accountIds);
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   ds
        *          The directory service.
        *
        * @return  The client config value as read from directory.
        */
       public T read(T defaultValue, DirectoryService ds)
       {
          return read(null, null, defaultValue, ds, null, null);
       }

       /**
        * Reads client-side the config with the specified default value.
        *
        * @param   directory
        *          The remote directory API.
        * @param   search
        *          Must be one of {@link Directory#ID_ABSOLUTE} (if the given node ID includes all paths
        *          from the root of the directory tree to the node being read) or {@link Directory#ID_RELATIVE}.
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        *
        * @return  The client config value as read with the directory rpc.
        */
       public T readInClient(Directory directory, int search, T defaultValue)
       {
          // add more supported types when needed
          if (valueClass == Boolean.class)
          {
             return (T) directory.getBoolean(search, CLIENT_CONF_CONTAINER + nodeName, (Boolean) defaultValue);
          }
          return null;
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   webDriverType
        *          The web driver type.
        * @param   scope
        *          The directory scope.
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   ds
        *          The directory service.
        * @param   placeholderValue
        *          The value to replace the placeholder in the directory node name for the config.
        * @param   accountIds
        *          The account ids for the directory lookup.
        *
        * @return  The client config value as read from directory.
        */
       public T read(WebDriverType webDriverType,
                     Utils.DirScope scope,
                     T defaultValue,
                     DirectoryService ds,
                     String placeholderValue,
                     String[] accountIds)
       {
          String finalNodeName = placeholderValue != null ? String.format(nodeName, placeholderValue) : nodeName;

          if (type == Type.CFG_OVERRIDES && !local.get().areCfgOverridesInitialized.get() || type == Type.RUNTIME)
          {
             if (LOG.isLoggable(Level.INFO))
             {
                LOG.log(Level.INFO, "ConfigItem '%s' can't be read from directory. Type %s.", finalNodeName, type);
             }
             return null;
          }

          if (type == Type.CFG_OVERRIDES && local.get().areCfgOverridesInitialized.get())
          {
             if (valueClass != String.class)
             {
                LOG.log(Level.INFO,
                        "ConfigItem '%s' should be from the String.class to be read from cfgOverrides.",
                        finalNodeName);
                return null;
             }
             Map<String, String> cfgOverrides = local.get().cfgOverrides;
             return cfgOverrides.containsKey(this.optionName) ?
                (T) cfgOverrides.get(this.optionName) :
                defaultValue;
          }

          if (scope == null)
          {
             scope = Utils.DirScope.SERVER;
          }

          if (webDriverType != null)
          {
             String driverSpecificContainer = webDriverType.toString().toLowerCase();

             // first check the web driver type containers gui/, chui/, embedded/ in webClient
             String node = WEB_CLIENT_CONTAINER + driverSpecificContainer + "/" + finalNodeName;
             T value = readFullNode(node, scope, null, ds, accountIds);
             if (value != null)
             {
                return value;
             }

             // second check the web driver type containers gui/, chui/, embedded/ in clientConfig
             node = CLIENT_CONF_CONTAINER + driverSpecificContainer + "/" + finalNodeName;
             value = readFullNode(node, scope, null, ds, accountIds);
             if (value != null)
             {
                return value;
             }
          }

          if (type == Type.WEB_CLIENT)
          {
             // then check the webClient container
             T value = readFullNode(WEB_CLIENT_CONTAINER + finalNodeName, scope, null, ds, accountIds);
             if (value != null)
             {
                return value;
             }
          }

          finalNodeName = type == Type.SERVER_ACCOUNT ? finalNodeName : CLIENT_CONF_CONTAINER + finalNodeName;

          // last check the clientConfig container
          return readFullNode(finalNodeName, scope, defaultValue, ds, accountIds);
       }

       /**
        * Reads the client config with the specified default value.
        *
        * @param   fullNode
        *          The full name of the node.
        * @param   scope
        *          The directory scope.
        * @param   defaultValue
        *          The default value returned if no value found in directory.
        * @param   ds
        *          The directory service.
        * @param   accountIds
        *          The account ids for the directory lookup.
        *
        * @return  The client config value as read from directory.
        */
       private T readFullNode(String fullNode,
                              Utils.DirScope scope,
                              T defaultValue,
                              DirectoryService ds,
                              String[] accountIds)
       {
          if (valueClass == String.class)
          {
             return (T) Utils.getDirectoryNodeString(ds, fullNode, (String) defaultValue, scope, accountIds);
          }
          if (valueClass == Integer.class)
          {
             return (T) Utils.getDirectoryNodeInt(ds, fullNode, (Integer) defaultValue, scope, accountIds);
          }
          if (valueClass == Boolean.class)
          {
             return (T) Utils.getDirectoryNodeBoolean(ds, fullNode, (Boolean) defaultValue, scope, accountIds);
          }
          if (valueClass == List.class)
          {
             return (T) Utils.getDirectoryNodeStrings(ds, fullNode, scope, accountIds);
          }
          if (valueClass == Map.class)
          {
             return (T) Utils.getDirectoryNodeMap(ds, fullNode, null, scope, false, accountIds);
          }
          return null;
       }
    }