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;
   }
}