CB-2131 add ability to read external conf (#899)

* CB-2131 add ability to read/write remote config

* CB-2131 read/write additional properties

* CB-2131 read external product conf

* CB-2131 checkstyle
This commit is contained in:
Alexander Skoblikov
2022-06-18 11:22:41 +03:00
committed by GitHub
parent 2db3004a5b
commit a88f1dfb06
4 changed files with 220 additions and 143 deletions
@@ -28,6 +28,7 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.registry.BaseApplicationImpl;
import org.jkiss.dbeaver.registry.EclipseWorkspaceImpl;
@@ -120,8 +121,12 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
return LocalResourceController.builder(credentialsProvider).build();
}
protected Map<String, Object> getServerConfigProps(Map<String, Object> configProps) {
return JSONUtils.getObject(configProps, "server");
}
@SuppressWarnings("unchecked")
protected void patchConfigurationWithProperties(Map<String, Object> configProps, IVariableResolver varResolver) {
public static void patchConfigurationWithProperties(Map<String, Object> configProps, IVariableResolver varResolver) {
for (Map.Entry<String, Object> entry : configProps.entrySet()) {
Object propValue = entry.getValue();
if (propValue instanceof String) {
@@ -20,6 +20,10 @@ import io.cloudbeaver.model.app.WebApplication;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class WebAppUtils {
public static String getRelativePath(String path, String curDir) {
@@ -37,4 +41,32 @@ public class WebAppUtils {
return (WebApplication) DBWorkbench.getPlatform().getApplication();
}
@SuppressWarnings("unchecked")
public static Map<String, Object> mergeConfigurations(Map<String, Object> origin, Map<String, Object> additional) {
var resultConfig = new HashMap<String, Object>();
Set<String> rootKeys = new HashSet<>(origin.keySet());
rootKeys.addAll(additional.keySet());
for (var rootKey : rootKeys) {
var originValue = origin.get(rootKey);
var additionalValue = additional.get(rootKey);
if (originValue == null || additionalValue == null) {
var resultValue = originValue != null ? originValue : additionalValue;
resultConfig.put(rootKey, resultValue);
continue;
}
if (originValue instanceof Map) {
var resultValue = mergeConfigurations((Map<String, Object>) originValue, (Map<String, Object>) additionalValue);
resultConfig.put(rootKey, resultValue);
} else {
resultConfig.put(rootKey, additionalValue);
}
}
return resultConfig;
}
}
@@ -19,7 +19,6 @@ package io.cloudbeaver.server;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.stream.JsonWriter;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.provider.AuthProviderConfig;
import io.cloudbeaver.model.app.BaseWebApplication;
@@ -106,7 +105,7 @@ public class CBApplication extends BaseWebApplication {
private File homeDirectory;
// Configurations
private final Map<String, Object> productConfiguration = new HashMap<>();
protected final Map<String, Object> productConfiguration = new HashMap<>();
private final Map<String, Object> databaseConfiguration = new HashMap<>();
private final CBAppConfig appConfiguration = new CBAppConfig();
private Map<String, String> externalProperties = new LinkedHashMap<>();
@@ -478,6 +477,11 @@ public class CBApplication extends BaseWebApplication {
}
private void parseConfiguration(File configFile) throws DBException {
Map<String, Object> configProps = readConfiguration(configFile);
parseConfiguration(configProps);
}
private void parseConfiguration(Map<String, Object> configProps) throws DBException {
String homeFolder = System.getenv(CBConstants.ENV_CB_HOME);
if (CommonUtils.isEmpty(homeFolder)) {
homeFolder = System.getProperty("user.dir");
@@ -486,40 +490,11 @@ public class CBApplication extends BaseWebApplication {
homeFolder = ".";
}
homeDirectory = new File(homeFolder);
String productConfigPath = null;
CBAppConfig prevConfig = new CBAppConfig(appConfiguration);
// Stupid way to populate existing objects but ok google (https://github.com/google/gson/issues/431)
InstanceCreator<CBAppConfig> appConfigCreator = type -> appConfiguration;
InstanceCreator<DataSourceNavigatorSettings> navSettingsCreator = type -> (DataSourceNavigatorSettings) appConfiguration.getDefaultNavigatorSettings();
Gson gson = new GsonBuilder()
.setLenient()
.registerTypeAdapter(CBAppConfig.class, appConfigCreator)
.registerTypeAdapter(DataSourceNavigatorSettings.class, navSettingsCreator)
.create();
try (Reader reader = new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8)) {
Map<String, Object> configProps = JSONUtils.parseMap(gson, reader);
Gson gson = getGson();
try {
Map<String, Object> serverConfig = JSONUtils.getObject(configProps, "server");
String externalPropertiesFile = JSONUtils.getString(serverConfig, CBConstants.PARAM_EXTERNAL_PROPERTIES);
if (!CommonUtils.isEmpty(externalPropertiesFile)) {
Properties props = new Properties();
try (InputStream is = Files.newInputStream(Path.of(externalPropertiesFile))) {
props.load(is);
} catch (IOException e) {
log.error("Error loading external properties from " + externalPropertiesFile, e);
}
for (String propName : props.stringPropertyNames()) {
this.externalProperties.put(propName, props.getProperty(propName));
}
}
patchConfigurationWithProperties(configProps);
serverPort = JSONUtils.getInteger(serverConfig, CBConstants.PARAM_SERVER_PORT, serverPort);
serverHost = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_HOST, serverHost);
if (serverConfig.containsKey(CBConstants.PARAM_SERVER_URL)) {
@@ -552,14 +527,7 @@ public class CBApplication extends BaseWebApplication {
gson.fromJson(gson.toJsonTree(appConfig), CBAppConfig.class);
databaseConfiguration.putAll(JSONUtils.getObject(serverConfig, CBConstants.PARAM_DB_CONFIGURATION));
productConfigPath = WebAppUtils.getRelativePath(
JSONUtils.getString(
serverConfig,
CBConstants.PARAM_PRODUCT_CONFIGURATION,
CBConstants.DEFAULT_PRODUCT_CONFIGURATION
),
homeFolder
);
readProductConfiguration(serverConfig, gson, homeFolder);
String staticContentsFile = JSONUtils.getString(serverConfig, CBConstants.PARAM_STATIC_CONTENT);
if (!CommonUtils.isEmpty(staticContentsFile)) {
@@ -591,6 +559,19 @@ public class CBApplication extends BaseWebApplication {
appConfiguration.setAuthProvidersConfiguration(mergedAuthProviders);
}
patchConfigurationWithProperties(productConfiguration);
}
protected void readProductConfiguration(Map<String, Object> serverConfig, Gson gson, String homeFolder) throws DBException {
String productConfigPath = WebAppUtils.getRelativePath(
JSONUtils.getString(
serverConfig,
CBConstants.PARAM_PRODUCT_CONFIGURATION,
CBConstants.DEFAULT_PRODUCT_CONFIGURATION
),
homeFolder
);
if (!CommonUtils.isEmpty(productConfigPath)) {
File productConfigFile = new File(productConfigPath);
if (!productConfigFile.exists()) {
@@ -606,18 +587,58 @@ public class CBApplication extends BaseWebApplication {
}
// Add product config from runtime
{
File rtConfig = getRuntimeProductConfigFilePath().toFile();
if (rtConfig.exists()) {
log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'");
try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) {
productConfiguration.putAll(JSONUtils.parseMap(gson, reader));
} catch (Exception e) {
throw new DBException("Error reading product runtime configuration", e);
}
File rtConfig = getRuntimeProductConfigFilePath().toFile();
if (rtConfig.exists()) {
log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'");
try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) {
productConfiguration.putAll(JSONUtils.parseMap(gson, reader));
} catch (Exception e) {
throw new DBException("Error reading product runtime configuration", e);
}
}
patchConfigurationWithProperties(productConfiguration);
}
protected Map<String, Object> readConfiguration(File configFile) throws DBException {
try (Reader reader = new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8)) {
Map<String, Object> configProps = JSONUtils.parseMap(getGson(), reader);
patchConfigurationWithProperties(configProps); // patch original properties
readAdditionalConfiguration(configProps);
Map<String, Object> serverConfig = getServerConfigProps(configProps);
String externalPropertiesFile = JSONUtils.getString(serverConfig, CBConstants.PARAM_EXTERNAL_PROPERTIES);
if (!CommonUtils.isEmpty(externalPropertiesFile)) {
Properties props = new Properties();
try (InputStream is = Files.newInputStream(Path.of(externalPropertiesFile))) {
props.load(is);
} catch (IOException e) {
log.error("Error loading external properties from " + externalPropertiesFile, e);
}
for (String propName : props.stringPropertyNames()) {
this.externalProperties.put(propName, props.getProperty(propName));
}
}
patchConfigurationWithProperties(configProps); // patch again because properties can be changed
return configProps;
} catch (IOException e) {
throw new DBException("Error parsing server configuration", e);
}
}
private Gson getGson() {
// Stupid way to populate existing objects but ok google (https://github.com/google/gson/issues/431)
InstanceCreator<CBAppConfig> appConfigCreator = type -> appConfiguration;
InstanceCreator<DataSourceNavigatorSettings> navSettingsCreator = type -> (DataSourceNavigatorSettings) appConfiguration.getDefaultNavigatorSettings();
return new GsonBuilder()
.setLenient()
.registerTypeAdapter(CBAppConfig.class, appConfigCreator)
.registerTypeAdapter(DataSourceNavigatorSettings.class, navSettingsCreator)
.create();
}
protected void readAdditionalConfiguration(Map<String, Object> rootConfig) throws DBException {
}
protected void parseAdditionalServerConfiguration(Map<String, Object> serverConfig) throws DBException {
@@ -702,10 +723,9 @@ public class CBApplication extends BaseWebApplication {
// Re-load runtime configuration
try {
log.debug("Reloading application configuration");
File runtimeConfigFile = getRuntimeAppConfigFile();
if (runtimeConfigFile.exists()) {
log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]");
parseConfiguration(runtimeConfigFile);
Map<String, Object> runtimeConfigProps = readRuntimeConfigurationProperties();
if (!runtimeConfigProps.isEmpty()) {
parseConfiguration(runtimeConfigProps);
}
} catch (Exception e) {
throw new DBException("Error parsing configuration", e);
@@ -714,6 +734,15 @@ public class CBApplication extends BaseWebApplication {
configurationMode = CommonUtils.isEmpty(serverName);
}
private Map<String, Object> readRuntimeConfigurationProperties() throws DBException {
File runtimeConfigFile = getRuntimeAppConfigFile();
if (runtimeConfigFile.exists()) {
log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]");
return readConfiguration(runtimeConfigFile);
}
return Map.of();
}
protected void finishSecurityServiceConfiguration(@NotNull String adminName, @Nullable String adminPassword, @NotNull List<WebAuthInfo> authInfoList) throws DBException {
SecurityPluginService.finishConfiguration(adminName, adminPassword, authInfoList);
}
@@ -741,106 +770,116 @@ public class CBApplication extends BaseWebApplication {
}
}
private void saveRuntimeConfig(String newServerName, String newServerURL, long sessionExpireTime, CBAppConfig appConfig) throws DBException {
protected void saveRuntimeConfig(String newServerName, String newServerURL, long sessionExpireTime, CBAppConfig appConfig) throws DBException {
Map<String, Object> configurationProperties = collectConfigurationProperties(newServerName, newServerURL, sessionExpireTime, appConfig);
writeRuntimeConfig(configurationProperties);
}
private void writeRuntimeConfig(Map<String, Object> configurationProperties) throws DBException {
File runtimeConfigFile = getRuntimeAppConfigFile();
try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigFile), StandardCharsets.UTF_8)) {
Gson gson = new GsonBuilder()
.setLenient()
.setPrettyPrinting()
.create();
try (JsonWriter json = gson.newJsonWriter(out)) {
json.setLenient(true);
json.beginObject();
{
json.name("server");
json.beginObject();
if (!CommonUtils.isEmpty(newServerName)) {
JSONUtils.field(json, CBConstants.PARAM_SERVER_NAME, newServerName);
}
if (!CommonUtils.isEmpty(newServerURL)) {
JSONUtils.field(json, CBConstants.PARAM_SERVER_URL, newServerURL);
}
if (sessionExpireTime > 0) {
JSONUtils.field(json, CBConstants.PARAM_SESSION_EXPIRE_PERIOD, sessionExpireTime);
}
if (!CommonUtils.isEmpty(databaseConfiguration)) {
JSONUtils.serializeProperties(json, CBConstants.PARAM_DB_CONFIGURATION, databaseConfiguration);
}
json.endObject();
}
{
json.name("app");
json.beginObject();
JSONUtils.field(json, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled());
JSONUtils.field(json, "supportsCustomConnections", appConfig.isSupportsCustomConnections());
JSONUtils.field(json, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled());
JSONUtils.field(json, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled());
JSONUtils.field(json, "enableReverseProxyAuth", appConfig.isEnabledReverseProxyAuth());
JSONUtils.field(json, "forwardProxy", appConfig.isEnabledForwardProxy());
JSONUtils.field(json, CBConstants.PARAM_RESOURCE_MANAGER_ENABLED, appConfig.isResourceManagerEnabled());
Map<String, Object> resourceQuotas = appConfig.getResourceQuotas();
if (!CommonUtils.isEmpty(resourceQuotas)) {
JSONUtils.serializeProperties(json, CBConstants.PARAM_RESOURCE_QUOTAS, resourceQuotas);
}
{
// Save only differences in def navigator settings
DBNBrowseSettings navSettings = appConfig.getDefaultNavigatorSettings();
json.name("defaultNavigatorSettings");
json.beginObject();
if (navSettings.isShowSystemObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowSystemObjects())
JSONUtils.field(json, "showSystemObjects", navSettings.isShowSystemObjects());
if (navSettings.isShowUtilityObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowUtilityObjects())
JSONUtils.field(json, "showUtilityObjects", navSettings.isShowUtilityObjects());
if (navSettings.isShowOnlyEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowOnlyEntities())
JSONUtils.field(json, "showOnlyEntities", navSettings.isShowOnlyEntities());
if (navSettings.isMergeEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isMergeEntities())
JSONUtils.field(json, "mergeEntities", navSettings.isMergeEntities());
if (navSettings.isHideFolders() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideFolders())
JSONUtils.field(json, "hideFolders", navSettings.isHideFolders());
if (navSettings.isHideSchemas() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideSchemas())
JSONUtils.field(json, "hideSchemas", navSettings.isHideSchemas());
if (navSettings.isHideVirtualModel() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideVirtualModel())
JSONUtils.field(json, "hideVirtualModel", navSettings.isHideVirtualModel());
json.endObject();
}
if (appConfig.getEnabledFeatures() != null) {
JSONUtils.serializeStringList(json, "enabledFeatures", Arrays.asList(appConfig.getEnabledFeatures()), true);
}
if (appConfig.getEnabledAuthProviders() != null) {
JSONUtils.serializeStringList(json, "enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()), true);
}
if (appConfig.getEnabledDrivers() != null) {
JSONUtils.serializeStringList(json, "enabledDrivers", Arrays.asList(appConfig.getEnabledDrivers()), true);
}
if (appConfig.getDisabledDrivers() != null) {
JSONUtils.serializeStringList(json, "disabledDrivers", Arrays.asList(appConfig.getDisabledDrivers()), true);
}
if (!CommonUtils.isEmpty(appConfig.getPlugins())) {
JSONUtils.serializeProperties(json, "plugins", appConfig.getPlugins());
}
if (!CommonUtils.isEmpty(appConfig.getAuthProviderConfigurations())) {
json.name("authConfiguration");
gson.toJson(appConfig.getAuthProviderConfigurations(), Map.class, json);
}
json.endObject();
}
json.endObject();
}
gson.toJson(configurationProperties, out);
} catch (IOException e) {
throw new DBException("Error writing runtime configuration", e);
}
}
protected Map<String, Object> collectConfigurationProperties(
String newServerName,
String newServerURL,
long sessionExpireTime,
CBAppConfig appConfig
) {
Map<String, Object> rootConfig = new LinkedHashMap<>();
{
var serverConfigProperties = new LinkedHashMap<String, Object>();
rootConfig.put("server", serverConfigProperties);
if (!CommonUtils.isEmpty(newServerName)) {
serverConfigProperties.put(CBConstants.PARAM_SERVER_NAME, newServerName);
}
if (!CommonUtils.isEmpty(newServerURL)) {
serverConfigProperties.put(CBConstants.PARAM_SERVER_URL, newServerURL);
}
if (sessionExpireTime > 0) {
serverConfigProperties.put(CBConstants.PARAM_SESSION_EXPIRE_PERIOD, sessionExpireTime);
}
if (!CommonUtils.isEmpty(databaseConfiguration)) {
serverConfigProperties.put(CBConstants.PARAM_DB_CONFIGURATION, databaseConfiguration);
}
}
{
var appConfigProperties = new LinkedHashMap<String, Object>();
rootConfig.put("app", appConfigProperties);
appConfigProperties.put("anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled());
appConfigProperties.put("supportsCustomConnections", appConfig.isSupportsCustomConnections());
appConfigProperties.put("publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled());
appConfigProperties.put("adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled());
appConfigProperties.put("enableReverseProxyAuth", appConfig.isEnabledReverseProxyAuth());
appConfigProperties.put("forwardProxy", appConfig.isEnabledForwardProxy());
appConfigProperties.put(CBConstants.PARAM_RESOURCE_MANAGER_ENABLED, appConfig.isResourceManagerEnabled());
Map<String, Object> resourceQuotas = appConfig.getResourceQuotas();
appConfigProperties.put(CBConstants.PARAM_RESOURCE_QUOTAS, resourceQuotas);
{
// Save only differences in def navigator settings
DBNBrowseSettings navSettings = appConfig.getDefaultNavigatorSettings();
var navigatorProperties = new LinkedHashMap<String, Object>();
appConfigProperties.put("defaultNavigatorSettings", navigatorProperties);
if (navSettings.isShowSystemObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowSystemObjects()) {
navigatorProperties.put("showSystemObjects", navSettings.isShowSystemObjects());
}
if (navSettings.isShowUtilityObjects() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowUtilityObjects()) {
navigatorProperties.put("showUtilityObjects", navSettings.isShowUtilityObjects());
}
if (navSettings.isShowOnlyEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isShowOnlyEntities()) {
navigatorProperties.put("showOnlyEntities", navSettings.isShowOnlyEntities());
}
if (navSettings.isMergeEntities() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isMergeEntities()) {
navigatorProperties.put("mergeEntities", navSettings.isMergeEntities());
}
if (navSettings.isHideFolders() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideFolders()) {
navigatorProperties.put("hideFolders", navSettings.isHideFolders());
}
if (navSettings.isHideSchemas() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideSchemas()) {
navigatorProperties.put("hideSchemas", navSettings.isHideSchemas());
}
if (navSettings.isHideVirtualModel() != CBAppConfig.DEFAULT_VIEW_SETTINGS.isHideVirtualModel()) {
navigatorProperties.put("hideVirtualModel", navSettings.isHideVirtualModel());
}
}
if (appConfig.getEnabledFeatures() != null) {
appConfigProperties.put("enabledFeatures", Arrays.asList(appConfig.getEnabledFeatures()));
}
if (appConfig.getEnabledAuthProviders() != null) {
appConfigProperties.put("enabledAuthProviders", Arrays.asList(appConfig.getEnabledAuthProviders()));
}
if (appConfig.getEnabledDrivers() != null) {
appConfigProperties.put("enabledDrivers", Arrays.asList(appConfig.getEnabledDrivers()));
}
if (appConfig.getDisabledDrivers() != null) {
appConfigProperties.put("disabledDrivers", Arrays.asList(appConfig.getDisabledDrivers()));
}
if (!CommonUtils.isEmpty(appConfig.getPlugins())) {
appConfigProperties.put("plugins", appConfig.getPlugins());
}
if (!CommonUtils.isEmpty(appConfig.getAuthProviderConfigurations())) {
appConfigProperties.put("authConfiguration", appConfig.getAuthProviderConfigurations());
}
}
return rootConfig;
}
////////////////////////////////////////////////////////////////////////
// License management
@@ -38,4 +38,5 @@ public class ConfigurationUtils {
return true;
}
}