EnvironmentDaemon.java
/*
** Module : EnvironmentDaemon.java
** Abstract : Daemon to handle Progress 4GL compatible Windows environment handling
**
** Copyright (c) 2013-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 EVL 20130604 Created initial version.
** 002 EVL 20140327 Changing constructor parameter list to be able to request the server for
** properties.
** 003 CA 20140702 Added isCustomEnvironment and getEnvironmentName APIs.
** 004 MAG 20140908 Added getEnvironmentType APIs. Added return value to load() method.
** Change the unload() implementation. Implements read only environments loading.
** Added setDefaultEnvironment() which override default environment.
** 005 HC 20151124 Improved loading of boolean environment variables from Progress INI files and
** Windows Registry.
** EVL 20151130 Fix for image clipping regression. The server side received wrong pixels per
** row value because none started value was not replaced with default one.
** 006 CA 20180523 Fixed environment name, when used in maps: registries are case-insensitive,
** stanza INI are case-sensitive.
** 007 RFB 20220310 The currentEnv was not being set to the one being loaded. Ref. #6088.
** SVL 20220620 Fixed LOAD which effectively called USE. Support for USE("ininame") for the config file
** specified at the client startup.
** SBI 20221202 Added subscribe(String, String, Consumer<String>).
** 008 SAT 20231020 Updated getEnvironmentName() to return "" instead of null when env is null.
** 009 GBB 20231027 Fallback env for non-Win, non-ini LOAD, UNLOAD, SET, GET-KEY-VALUE, PUT-KEY-VALUE.
** 010 GBB 20231204 Adds base key to the fallback environment reader. Case-insensitivity improved.
** Instantiate the fallback reader as a default env for non-Win OS.
** 011 GBB 20240826 Moving getOperatingSystem() and getCurrentPrinterName() here from OsPropertiesDaemon.
** Removing dependency on Session. Replacing env prop subscription mechanism rpc from server.
*/
/*
** 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.osresource;
import java.io.*;
import java.util.*;
import java.util.function.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.ui.client.*;
import com.goldencode.p2j.ui.client.widget.*;
import com.goldencode.p2j.util.*;
import com.goldencode.util.*;
import javax.print.*;
/**
* A class to manipulate environments. The environment can be the INI file, registry sections, java user
* Preferences or localStorage.
*
* @author EVL
*/
class EnvironmentDaemon
implements EnvironmentAccessor
{
/** The default environment to use when load() and use() was not specified. */
private EnvironmentReader defaultEnv = null;
/** The currently loaded environment to use instead of default. */
private EnvironmentReader currentEnv = null;
/** Map to store registry environments for possible reuse. */
private Map<String, EnvironmentReader> envRegMap = new HashMap<String, EnvironmentReader>();
/** Map to store INI environments for possible reuse. */
private Map<String, EnvironmentReader> envIniMap = new HashMap<String, EnvironmentReader>();
/** Map to store Fallback environments for possible reuse. */
private Map<String, EnvironmentReader> envFallbackMap = new HashMap<String, EnvironmentReader>();
/** INI files read only property */
private boolean isReadOnly = false;
/** Token used to authenticate with the dispatcher when registering APIs. */
private Object modToken = null;
/** ClientStorage instance. */
private final ClientStorage clientStorage;
/**
* Create an instance and export its API to the network.
*
* @param envVersion
* The current version string of the Progress compatible environment.
* @param local
* <code>true</code> to startup within the server process which must bypass the
* shared infrastructure initialization. Use <code>false</code> for the normal
* client JVM startup.
* @param isWin
* <code>true</code> if this is an WIN32 environment backed up by a default.
* @param clientStorage
* ClientStorage instance.
*/
EnvironmentDaemon(String envVersion, boolean local, boolean isWin, ClientStorage clientStorage)
{
this.clientStorage = clientStorage;
isReadOnly = getReadOnly();
defaultEnv = isWin ?
new Registry(envVersion) :
new FallbackEnvironmentReader("__default", clientStorage, null);
modToken = RemoteObject.registerServer(EnvironmentAccessor.class, this, modToken, local);
}
/**
* Creates application defaults implementing Progress LOAD statement.
*
* @param env
* The environment to create or load. Can be the registry key, initialization file.
* @param directory
* The directory path for existing initialization file or place to create
* initialization file.
* @param flagNew
* If <code>true</code> - the new file or registry key will be created overwriting the
* possibly existed one.
* @param baseKey
* When the parameter is specified the searching is performed under the particular key
* value. It is possible to specify "INI" as parameter. In this case search or creation
* will be done inside initialization file specified in env parameter.
*
* @return <code>true</code> if loaded false otherwise
*/
@Override
public boolean load(String env,
String directory,
boolean flagNew,
String baseKey)
{
EnvironmentReader envAcc;
Map<String, EnvironmentReader> envs;
boolean isIniType = EnvironmentReader.TYPE_INI.equalsIgnoreCase(baseKey);
if (!isIniType && !PlatformHelper.isUnderWindowsFamily())
{
envs = envFallbackMap;
env = env.toLowerCase();
envAcc = new FallbackEnvironmentReader(env, clientStorage, baseKey.toLowerCase());
}
else if (isIniType)
{
envs = envIniMap;
envAcc = new StanzaIni(env, directory, flagNew, isReadOnly);
}
else
{
envs = envRegMap;
env = env.toLowerCase();
envAcc = new Registry(env, flagNew, baseKey);
}
boolean loaded = envAcc.load();
if (loaded)
{
envs.put(env, envAcc);
}
return loaded;
}
/**
* Specifies environment defaults to use in subsequent windows.
*
* @param env
* The environment to load. If the parameter is empty string ("") the default
* environment becomes the current one.
*
* @return The loaded environment's name, as it was saved on LOAD. <code>null</code> if the
* environment can't be found.
*/
@Override
public String use(String env)
{
// Empty string as environment makes the default environment the current one
if (env == null || env.isEmpty())
{
currentEnv = null;
return "";
}
// registry first
currentEnv = envRegMap.get(env.toLowerCase());
if (currentEnv != null)
{
return currentEnv.getEnvironmentName();
}
String envIni = env.indexOf('.') >= 0 ? env : env + ".ini";
// default ini second
if (defaultEnv != null && envIni.equals(defaultEnv.getEnvironmentName()))
{
currentEnv = null;
return "";
}
// stanza third
currentEnv = envIniMap.get(envIni);
if (currentEnv != null)
{
return currentEnv.getEnvironmentName();
}
// fallback
currentEnv = envFallbackMap.get(env.toLowerCase());
if (currentEnv != null)
{
return currentEnv.getEnvironmentName();
}
// not found
return null;
}
/**
* Check if a custom environment is in use.
*
* @return See above.
*/
@Override
public boolean isCustomEnvironment()
{
return currentEnv != null;
}
/**
* Get the current environment's name.
*
* @return See above.
*/
@Override
public String getEnvironmentName()
{
EnvironmentReader env = (currentEnv == null ? defaultEnv : currentEnv);
return (env == null ? "" : env.getEnvironmentName());
}
/**
* Get the current environment's type.
*
* @return environment type.
*/
@Override
public String getEnvironmentType()
{
EnvironmentReader env = (currentEnv == null ? defaultEnv : currentEnv);
return (env == null ? null : env.getEnvironmentType());
}
/**
* Set new value for keys in the current environment or the default environment if
* the current one is not loaded.
*
* @param section
* The name of the section containing the key to modify.
* @param key
* The name of the key to modify
* @param value
* The new value of the key under modification.
*/
@Override
public void setKeyValue(String section, String key, String value)
{
EnvironmentReader envReader = currentEnv != null ? currentEnv : defaultEnv;
if (envReader == null)
{
return;
}
if (!envReader.getEnvironmentType().equals(EnvironmentReader.TYPE_FALLBACK) &&
(section == null || value == null))
{
return;
}
if (!envReader.getEnvironmentType().equals(EnvironmentReader.TYPE_INI))
{
section = section.toLowerCase();
key = key == null ? null : key.toLowerCase();
}
envReader.setKeyValue(section, key, value);
}
/**
* Getting the key value from the current environment or default environment if the current one
* is not loaded.
*
* @param section
* The name of the section containing the key to get.
* @param key
* The name of the key to get.
*
* @return The value of the key.
*/
@Override
public String getKeyValue(String section, String key)
{
EnvironmentReader envReader = currentEnv != null ? currentEnv : defaultEnv;
if (section == null || envReader == null)
{
return "";
}
if (!envReader.getEnvironmentType().equals(EnvironmentReader.TYPE_INI))
{
section = section.toLowerCase();
key = key == null ? null : key.toLowerCase();
}
return envReader.getKeyValue(section, key);
}
/**
* Getting the boolean key value from the current environment.
*
* @param section
* The name of the section containing the key to get.
* @param key
* The name of the key key to get or default key if not specified.
* @param defValue
* The default value, to be returned when the value does not exist
* in the environment.
*
* @return The boolean key value, <code>defValue</code> if the value
* does not exist in the environment.
*/
@Override
public Boolean getBooleanKeyValue(String section, String key, Boolean defValue)
{
String strValue = getKeyValue(section, key);
// if no value for the key - return default one
if (strValue == null || strValue.isEmpty())
{
return defValue;
}
// the only valid boolean true for Progress INI file is the case insensitive 'yes'
// TODO: is this compatible with the allowed boolean values in Windows registry?
return strValue.trim().equalsIgnoreCase("yes");
}
/**
* Unloads specifies environment from the current one.
*
* @param env
* The environment to unload. Can be the registry or initialization file. The
* environment to unload should be previously loaded by load() method.
*
* @return The loaded environment's name, as it was saved on LOAD. <code>null</code> if the
* environment can't be found.
*/
@Override
public String unload(String env)
{
if (env == null)
{
return null;
}
Map<String, EnvironmentReader> envs = null;
if (envRegMap.containsKey(env.toLowerCase()))
{
envs = envRegMap;
env = env.toLowerCase();
}
else
{
String envIni = env.indexOf('.') >= 0 ? env : env + ".ini";
if (envIniMap.containsKey(envIni))
{
env = envIni;
envs = envIniMap;
}
}
if (envs == null && !PlatformHelper.isUnderWindowsFamily() && envFallbackMap.containsKey(env.toLowerCase()))
{
envs = envFallbackMap;
env = env.toLowerCase();
}
for (TitledWindow<?> window : WindowManager.windowList())
{
WindowConfig cfg = window.config();
if (cfg != null && env.equalsIgnoreCase(cfg.envName))
{
return null;
}
}
if (envs == null)
{
return null;
}
EnvironmentReader envReader = envs.remove(env);
envReader.unload();
// set current environment to default if unloaded environment was current environment
if (currentEnv.getEnvironmentType().equals(EnvironmentReader.TYPE_INI) && env.equals(currentEnv.getEnvironmentName()) ||
env.equalsIgnoreCase(currentEnv.getEnvironmentName()))
{
currentEnv = null;
}
// notify about unload
ColorManager.unload(env);
FontManager.unload(env);
return envReader.getEnvironmentName();
}
/**
* Sets the default environment to a stanza ini file, overwriting any default registry for
* WIN32.
*
* @param iniFile
* The name of the ini file backing the stanzas.
*/
@Override
public void setDefaultEnvironment(String iniFile)
{
File file = new File(iniFile);
// check if file exists when the file is not loaded from server.
if (!isReadOnly && !file.exists())
{
// client aborts if environment file is not found
throw new RuntimeException(iniFile + " file not found. (5643)");
}
// directory
String dir = file.getParent();
// file name
String fileName = file.getName();
// load the file and store it as the default environment
EnvironmentReader reader = new StanzaIni(fileName, dir, false, isReadOnly);
if (reader.load())
{
defaultEnv = reader;
}
else
{
// client aborts if environment file is not loaded
throw new RuntimeException(iniFile + " file not found. (5643)");
}
}
/**
* If there is still no match, then any <code>os.name</code> with the
* lowercase text "unix" will return "UNIX" and any <code>os.name</code>
* with the lowercase text "windows" will return "WIN32".
* <p>
* If there is still no match, the <code>os.name</code> returned by the
* Java will be returned.
*
* @return The name of the current operating system.
*/
@Override
public String getOperatingSystem()
{
return System.getProperty("os.name").toLowerCase();
}
/**
* Get the name of the current printer on the client side.
*
* @return the printer name.
*/
@Override
public String getCurrentPrinterName()
{
PrintService prnService = PrintServiceLookup.lookupDefaultPrintService();
return prnService != null ? prnService.getName() : null;
}
/**
* Get environments read only flag. When read only flag is set to <code>true</code> the
* environments INI files are loaded as resources from remote server jar files otherwise
* will follow default behavior and INI files are loaded from client file system.
*
* @return Read only flag (see above).
*/
private boolean getReadOnly()
{
boolean result = false;
Directory remoteDir = DirectoryManager.getInstance();
if (remoteDir != null)
{
result = remoteDir.getBoolean(Directory.ID_RELATIVE, "readOnlyEnvironments", false);
}
return result;
}
}