# WebClientBuilderOptions.java

    /*
    ** Module   : WebClientBuilderOptions.java
    ** Abstract : store web client parameters read from the directory
    **
    ** Copyright (c) 2014-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 CA  20140206 First version, extracted from ClientBuilderOptions.
    ** 002 MAG 20140711 Enable/Disable clipboard.
    ** 003 GES 20150310 Made this class generic for web clients, not just for ChUI web clients.
    ** 004 GES 20150717 Added some GUI configuration values.
    ** 005 GES 20160202 Added trusted mode support and removed some dead code.
    **     SBI 20160321 Added maxBinaryMessage, maxTextMessage, maxIdleTime websocket's parameters and
    **                  utilized "webClient/embedded" to set the web client embedded mode.
    ** 006 SBI 20160427 Added graphicsCached parameter to turn off the graphics cache for testing.
    ** 007 SBI 20170628 Fixed misspelled variable to set watchdogTimeout correctly.
    ** 007 CA  20191211 When spawning a Java process programmatically, allow the caller to pass
    **                  environment options specific to that call (beside any jvmArgs configured in
    **                  the directory).
    ** 008 HC  20201116 Added renderer web client option. Made WebGL the default renderer.
    **     SBI 20210331 Added new taskBarStyle web client option.
    **     SBI 20210514 Added delayBetweenTriesToConnect, tryToConnectMessage and serverUnavailableMessage options.
    **         20210516 Added pingPongInterval, maxLostPings and delayBetweenPingTries options.
    **         20210517 Added connectionRestoredMessage option.
    **     HC  20210617 Made the default Web client renderer Canvas 2D non-cached.
    **     SBI 20210914 Added enableDebugLogging.
    **         20210921 Updated JVM arguments for new client.
    **     CA  20210924 Replaced 'sticked' with 'fixed' name, for web client taskbar style.
    **     SBI 20210926 Removed code that updates jvm arguments for new client.
    **     SBI 20211003 Added http configuration settings for the web client.
    **     TJD 20220527 Support for text-metrics cache controlled by server
    ** 009 SBI 20230411 Added lang option and changed getClientOptions to use getOverriddenOptions()
    ** 010 GBB 20230825 Cleanup for embedded.
    ** 011 GBB 20231121 Added broadcastChannelPingTimeout.
    ** 012 SBI 20231208 Used the common constants.
    ** 013 GBB 20240709 Implemented interface HttpConfigurationConstants renamed to WebConfigurationConstants,
    **                  while WebClientConstants removed. Hard-coded config names replaced by ConfigItem
    **                  constants. tokenConfigsCachedPairs to cache the last 10 options resolved by project
    **                  token. Overridden options to be stored in ThreadLocal and allow reset to replace copying
    **                  the whole object for each spawn request.
    ** 014 GBB 20240718 Config read implemented in concrete class BaseClientBuilderOptions.
    ** 015 GBB 20240730 Resolve and store the project token for the web driver.
    ** 016 SBI 20240828 Added default value WATCH_DOG_TIMEOUT for watch dog timeout instead of -1.
     */
    /*
    ** 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.main;

    import java.util.*;

    import com.goldencode.p2j.net.*;
    import com.goldencode.p2j.ui.*;
    import com.goldencode.p2j.ui.client.chui.driver.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.web.*;

    /**
     * The parameters stored inside this structure are read from directory on server start-up
     * and are used as default parameters to create and spawn a web client process.
     * <p>
     * This class MUST NOT BE USED from client side, as it uses a direct access to the server.  Also,
     * this class can be instantiated only from threads with a P2J context for the running P2J server.
     */
    public class WebClientBuilderOptions
    implements ClientBuilderOptions,
               WebConfigurationConstants
    {
       /**
        * Cache for configs (resolved by {@link #readConfigs()}), stored by project token. The values are
        * unmodifiable maps of {@link ConfigItem#name()} : value.
        */
       private static final Map<String, Map<String, String>> tokenConfigsCachedPairs =
          new LinkedHashMap<String, Map<String, String>>() {
             @Override
             protected boolean removeEldestEntry(Map.Entry eldest)
             {
                return size() > 10;
             }
          };

       /** Options storage. Unmodifiable map. */
       private final Map<String, String> dirOptions;

       /** Base options. */
       private final BaseClientBuilderOptions baseOptions;

       /** Storage for overridden options. Different on the different Jetty threads. To be reset. */
       private final ThreadLocal<Map<String, String>> overriddenOptions = ThreadLocal.withInitial(HashMap::new);

       /** The web driver type. Can't be in WebClientBuilderOptions to resolve the parent options. */
       private final WebDriverType driverType;

       /** Project token */
       private final String projectToken;

       /**
        * Public constructor. Reads the configuration from the directory and calculates certain runtime values
        * that will be used by web client launching.
        *
        * @param    driverType
        *           The web driver type: gui, chui, embedded.
        *
        * @throws   IllegalStateException
        *           If this is called from P2J clients.
        */
       public WebClientBuilderOptions(WebDriverType driverType)
       {
          this.driverType = driverType;

          this.projectToken = ConfigItem.PROJECT_TOKEN.read(null, driverType);

          if (SessionManager.get() == null || SessionManager.get().isLeaf())
          {
             throw new IllegalStateException("This class can be used only on the server-side!");
          }
          // TODO: this will not allow changing the options via the AdminConsole - add a way to
          // invalidate and recreate this instance

          this.dirOptions = readConfigs();

          this.baseOptions = new BaseClientBuilderOptions(null)
          {
             /**
              * A convenience method to read the client config with the specified default value.
              *
              * @param   clientConfig
              *          The client config to read.
              * @param   defaultValue
              *          The default value returned if no value found in directory.
              * @param   <T>
              *          The type of the client config.
              *
              * @return  The client config value as read from directory.
              */
             @Override
             <T> T read(ConfigItem<T> clientConfig, T defaultValue)
             {
                return clientConfig.read(defaultValue, driverType);
             }

             /**
              * A convenience method to read the client config with the specified default value.
              *
              * @param   clientConfig
              *          The client config to read.
              * @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   <T>
              *          The type of the client config.
              *
              * @return  The client config value as read from directory.
              */
             @Override
             <T> T read(ConfigItem<T> clientConfig, T defaultValue, String placeholderValue)
             {
                return clientConfig.read(defaultValue, driverType, placeholderValue);
             }

             /**
              * Reads and returns the value of clientConfig/cfgOverrides.
              *
              * @param   defaultValue
              *          The default value returned if no value found in directory.
              *
              * @return  The cfgOverrides value.
              */
             @Override
             String readCfgOverrides(String defaultValue)
             {
                return ConfigItem.CFG_OVERRIDES.read(defaultValue);
             }
          };
       }

       /**
        * Resets the thread local value of {@link #overriddenOptions}.
        */
       public void reset()
       {
          baseOptions.reset();
          overriddenOptions.set(new HashMap<>());
       }

       /**
        * Returns the {@link #driverType}.
        *
        * @return   See above.
        */
       public WebDriverType getDriverType()
       {
          return driverType;
       }

       /**
        * Returns the {@link #projectToken}.
        *
        * @return   See above.
        */
       public String getProjectToken()
       {
          return projectToken;
       }

       /**
        * Reads the configuration from the directory and calculates certain runtime values that will be used by
        * web client launching.
        *
        * @return   Unmodifiable map of options.
        */
       static Map<String, String> readConfigs()
       {
          String projectToken = Utils.getProjectToken();
          if (projectToken != null && tokenConfigsCachedPairs.containsKey(projectToken))
          {
             return tokenConfigsCachedPairs.get(projectToken);
          }

          Map<String, String> options = new HashMap<>();

          // ChUI terminal size
          add(options, ConfigItem.ROWS, TerminalOptions.DEF_ROWS);
          add(options, ConfigItem.COLS, TerminalOptions.DEF_COLUMNS);

          // GUI desktop size in pixels
          add(options, ConfigItem.DESKTOP_HEIGHT, 768);
          add(options, ConfigItem.DESKTOP_WIDTH, 1024);
          // for colors, don't include any alpha on bits 24-31, if emitted as FF the result is not
          // interpreted properly by the browser
          add(options, ConfigItem.BACKGROUND, TerminalOptions.DEF_BACKGROUND.asHexString());
          add(options, ConfigItem.FOREGROUND, TerminalOptions.DEF_FOREGROUND.asHexString());
          add(options, ConfigItem.SELECTION, TerminalOptions.DEF_SELECTION.asHexString());
          add(options, ConfigItem.FONT_NAME, TerminalOptions.DEF_FONTNAME, true);
          add(options, ConfigItem.FONT_SIZE, TerminalOptions.DEF_FONTSIZE);
          add(options, ConfigItem.WEB_SOCKET_TIMEOUT, MAX_WEB_SOCKET_IDLE_TIMEOUT);
          add(options, ConfigItem.WATCHDOG_TIMEOUT, WATCH_DOG_TIMEOUT);
          add(options, ConfigItem.PORT, 0);
          add(options, ConfigItem.HOST, null);
          add(options, ConfigItem.CLIPBOARD, true);
          add(options, ConfigItem.TASKBAR, true);
          add(options, ConfigItem.TASKBAR_STYLE, "fixed");
          add(options, ConfigItem.USE_LOCALSTORAGE, false);
          add(options, ConfigItem.BROADCAST_CHANNEL_PING_TIMEOUT, -1);

          // common websocket parameters
          add(options, ConfigItem.MAX_BINARY_MSG_SIZE, MAX_BINARY_MESSAGE_SIZE);
          add(options, ConfigItem.MAX_TEXT_MSG_SIZE, MAX_TEXT_MESSAGE_SIZE);
          add(options, ConfigItem.MAX_IDLE_TIME, MAX_WEB_SOCKET_IDLE_TIMEOUT);
          add(options, ConfigItem.CONNECT_RETRY_DELAY, DELAY_BETWEEN_CONNECT_TRIES);
          add(options, ConfigItem.PING_DELAY, DELAY_BETWEEN_PING_TRIES);
          add(options, ConfigItem.PING_PONG_INTERVAL, PING_PONG_INTERVAL);
          add(options, ConfigItem.MAX_LOST_PINGS, MAX_LOST_PINGS);
          add(options, ConfigItem.TRY_CONNECT_MSG, TRY_TO_CONNECT_MESSAGE, true);
          add(options, ConfigItem.SERVER_UNAVAILABLE_MSG, SERVER_UNAVAILABLE_MESSAGE, true);
          add(options, ConfigItem.CONNECTION_RESTORED_MSG, CONNECTION_RESTORED_MESSAGE, true);

          // http configuration settings for the embedded web server
          add(options, ConfigItem.MAX_OUT_BUFFER_SIZE, OUTPUT_BUFFER_SIZE);
          add(options, ConfigItem.MAX_OUT_AGGREGATION_SIZE, OUTPUT_AGGREGATION_SIZE);
          add(options, ConfigItem.MAX_HTTP_IDLE_TIME, INFINITE_TIMEOUT);
          add(options, ConfigItem.MAX_RESPONSE_HEADER_SIZE, RESPONSE_HEADER_SIZE);
          add(options, ConfigItem.MAX_REQUEST_HEADER_SIZE, REQUEST_HEADER_SIZE);

          // the possible values for renderer are webgl and 2d (case insensitive)
          String renderer = ConfigItem.RENDERER.read("2d").trim().toLowerCase();
          options.put(ConfigItem.RENDERER.name(), renderer);

          // Read if the drawing batches are cached
          // sets true if the web client uses a buffer to cache drawings, otherwise false.
          add(options, ConfigItem.GRAPHICS_CACHED, false);

          add(options, ConfigItem.DISABLE_PIXEL_MANIPULATION, false);

          // Get client font metrics cache size from configuration
          int clientTextMetricsCacheSize =
             Utils.getDirectoryNodeInt(null,
                                       FontTable.FONT_METRICS_NODE_ID + "/" + ConfigItem.CACHE_SIZE.name(),
                                       0,
                                       false);
          options.put(ConfigItem.CACHE_SIZE.name(), String.valueOf(clientTextMetricsCacheSize));

          options.put(ConfigItem.LANG.name(), EnvironmentOps.getCurrentLanguageString());

          options.put(ConfigItem.JS_DEBUG_LOGGING.name(), "false");

          options = Collections.unmodifiableMap(options);

          if (projectToken != null)
          {
             tokenConfigsCachedPairs.put(projectToken, options);
          }

          return options;
       }

       /**
        * Returns the base client options.
        *
        * @return   The base client options.
        */
       @Override
       public BaseClientBuilderOptions getBaseOptions()
       {
          return baseOptions;
       }

       /**
        * Add the given named option to the overridden options map.
        *
        * @param    name
        *           The option name
        * @param    value
        *           The option value
        */
       void override(String name, String value)
       {
          overriddenOptions.get().put(name, value);
       }

       /**
        * Returns the value stored for the client config parameter. First checks the overridden options and
        * then falls back to the default options.
        *
        * @param    clientConfig
        *           The client config.
        *
        * @return   The value stored for the client config parameter.
        */
       String get(ConfigItem clientConfig)
       {
          String configName = clientConfig.name();
          if (overriddenOptions.get().containsKey(configName))
          {
             return overriddenOptions.get().get(configName);
          }
          if (dirOptions.containsKey(configName))
          {
             return dirOptions.get(configName);
          }
          return null;
       }

       /**
        * Returns the default web client options.
        *
        * @return   See above.
        */
       Map<String, String> getDefaults()
       {
          return dirOptions;
       }

       /**
        * Returns the default web client options.
        *
        * @return   See above.
        */
       Map<String, String> getOverriddenOptions()
       {
          return overriddenOptions.get();
       }

       /**
        * Convenience method to add to the options map the name and value of the client config.
        *
        * @param    options
        *           The options map of client config name : value pairs.
        * @param    clientConfig
        *           The client config.
        * @param    defaultValue
        *           The default value.
        * @param    <T>
        *           The type of the client config value.
        */
       private static <T> void add(Map<String, String> options, ConfigItem<T> clientConfig, T defaultValue)
       {
          add(options, clientConfig, defaultValue, false);
       }

       /**
        * Convenience method to add to the options map the name and value of the client config.
        *
        * @param    options
        *           The options map of client config name : value pairs.
        * @param    clientConfig
        *           The client config.
        * @param    defaultValue
        *           The default value.
        * @param    quoteStrings
        *           Flag to add quotes to the string.
        * @param    <T>
        *           The type of the client config value.
        */
       private static <T> void add(Map<String, String> options,
                                   ConfigItem<T> clientConfig,
                                   T defaultValue,
                                   boolean quoteStrings)
       {
          String value = String.valueOf(clientConfig.read(defaultValue));
          options.put(clientConfig.name(), quoteStrings ? BaseClientBuilderOptions.quotedString(value) : value);
       }
    }