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