com.marklogic.appdeployer.DefaultAppConfigFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ml-app-deployer Show documentation
Show all versions of ml-app-deployer Show documentation
Java client for the MarkLogic REST Management API and for deploying applications to MarkLogic
The newest version!
/*
* Copyright (c) 2023 MarkLogic Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.marklogic.appdeployer;
import com.marklogic.appdeployer.util.JavaClientUtil;
import com.marklogic.client.DatabaseClient;
import com.marklogic.client.ext.SecurityContextType;
import com.marklogic.mgmt.util.PropertySource;
import com.marklogic.mgmt.util.PropertySourceFactory;
import org.springframework.util.StringUtils;
import java.io.File;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
public class DefaultAppConfigFactory extends PropertySourceFactory implements AppConfigFactory {
private File projectDir;
public DefaultAppConfigFactory() {
super();
initialize();
}
public DefaultAppConfigFactory(PropertySource propertySource) {
super(propertySource);
initialize();
}
private Map> propertyConsumerMap;
@Override
public AppConfig newAppConfig() {
final AppConfig appConfig = new AppConfig(this.projectDir);
for (String propertyName : propertyConsumerMap.keySet()) {
String value = getProperty(propertyName);
if (value != null) {
try {
propertyConsumerMap.get(propertyName).accept(appConfig, value);
} catch (Exception ex) {
throw new IllegalArgumentException(
format("Unable to parse value '%s' for property '%s'; cause: %s", value, propertyName, ex.getMessage()), ex);
}
}
}
return appConfig;
}
/**
* Registers all of the property handlers.
*/
public void initialize() {
// Order matters, so a LinkedHashMap is used to preserve the order
propertyConsumerMap = new LinkedHashMap<>();
propertyConsumerMap.put("mlCatchDeployExceptions", (config, prop) -> {
logger.info("Catch deploy exceptions: " + prop);
config.setCatchDeployExceptions(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlCatchUndeployExceptions", (config, prop) -> {
logger.info("Catch undeploy exceptions: " + prop);
config.setCatchUndeployExceptions(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlMergeResources", (config, prop) -> {
logger.info("Merge resources before saving them: " + prop);
config.setMergeResources(Boolean.parseBoolean(prop));
});
final String cmaMessage = " with the Configuration Management API (CMA): ";
propertyConsumerMap.put("mlDeployWithCma", (config, prop) -> {
logger.info("Deploy all supported resources and combine requests" + cmaMessage + prop);
if (Boolean.parseBoolean(prop)) {
config.getCmaConfig().enableAll();
} else {
config.setCmaConfig(new CmaConfig());
}
});
propertyConsumerMap.put("mlOptimizeWithCma", (config, prop) -> {
logger.info("mlOptimizeWithCma is DEPRECATED; please use a property specific to the resource that you want to deploy with CMA");
// mlOptimizeWithCma was deprecated in 3.11; it was only used for deploying forests, so if the
// property is still used, the client in theory expects forests to still be deployed with CMA
config.getCmaConfig().setDeployForests(true);
});
propertyConsumerMap.put("mlCombineCmaRequests", (config, prop) -> {
logger.info("Combine requests" + cmaMessage + prop);
config.getCmaConfig().setCombineRequests(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeployAmpsWithCma", (config, prop) -> {
logger.info("Deploy amps" + cmaMessage + prop);
config.getCmaConfig().setDeployAmps(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeployDatabasesWithCma", (config, prop) -> {
logger.info("Deploy databases and forests" + cmaMessage + prop);
config.getCmaConfig().setDeployDatabases(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeployForestsWithCma", (config, prop) -> {
logger.info("Deploy forests" + cmaMessage + prop);
config.getCmaConfig().setDeployForests(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeployPrivilegesWithCma", (config, prop) -> {
logger.info("Deploy privileges" + cmaMessage + prop);
config.getCmaConfig().setDeployPrivileges(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeployProtectedPathsWithCma", (config, prop) -> {
logger.info("Deploy protected paths" + cmaMessage + prop);
config.getCmaConfig().setDeployProtectedPaths(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeployQueryRolesetsWithCma", (config, prop) -> {
logger.info("Deploy query rolesets" + cmaMessage + prop);
config.getCmaConfig().setDeployQueryRolesets(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeployRolesWithCma", (config, prop) -> {
logger.info("Deploy servers" + cmaMessage + prop);
config.getCmaConfig().setDeployRoles(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeployServersWithCma", (config, prop) -> {
logger.info("Deploy servers" + cmaMessage + prop);
config.getCmaConfig().setDeployServers(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeployUsersWithCma", (config, prop) -> {
logger.info("Deploy users" + cmaMessage + prop);
config.getCmaConfig().setDeployUsers(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlAddHostNameTokens", (config, prop) -> {
logger.info("Add host names to the custom tokens map: " + prop);
config.setAddHostNameTokens(Boolean.parseBoolean(prop));
});
/**
* The application name is used as a prefix for default names for a variety of resources, such as REST API servers
* and databases.
*/
propertyConsumerMap.put("mlAppName", (config, prop) -> {
logger.info("App name: " + prop);
config.setName(prop);
});
/**
* The path to the directory containing all the resource configuration files. Defaults to src/main/ml-config.
* mlConfigPath is the preferred one, as its name is consistent with other properties that refer to a path.
* mlConfigDir is deprecated but still supported.
*
* As of 3.3.0, mlConfigPaths is the preferred property, and mlConfigDir and mlConfigPath will be ignored if
* it's set.
*/
propertyConsumerMap.put("mlConfigPaths", (config, prop) -> {
logger.info("Config paths: " + prop);
List list = new ArrayList<>();
for (String path : prop.split(",")) {
list.add(buildConfigDir(path));
}
config.setConfigDirs(list);
});
// TODO Only process if mlConfigPaths not set?
propertyConsumerMap.put("mlConfigDir", (config, prop) -> {
logger.info("mlConfigDir is deprecated; please use mlConfigPath; Config dir: " + prop);
config.setConfigDir(buildConfigDir(prop));
});
propertyConsumerMap.put("mlConfigPath", (config, prop) -> {
logger.info("Config path: " + prop);
config.setConfigDir(buildConfigDir(prop));
});
/**
* Defines the MarkLogic host that requests should be sent to. Defaults to localhost.
*/
propertyConsumerMap.put("mlHost", (config, prop) -> {
logger.info("App host: " + prop);
config.setHost(prop);
});
propertyConsumerMap.put("mlCloudApiKey", (config, prop) -> {
logger.info("Setting cloud API key");
config.setCloudApiKey(prop);
});
propertyConsumerMap.put("mlKeyStorePath", (config, prop) -> {
logger.info("REST and App-Services key store path: " + prop);
config.setRestKeyStorePath(prop);
config.setAppServicesKeyStorePath(prop);
});
propertyConsumerMap.put("mlKeyStorePassword", (config, prop) -> {
config.setRestKeyStorePassword(prop);
config.setAppServicesKeyStorePassword(prop);
});
propertyConsumerMap.put("mlKeyStoreType", (config, prop) -> {
logger.info("REST and App-Services key store type: " + prop);
config.setRestKeyStoreType(prop);
config.setAppServicesKeyStoreType(prop);
});
propertyConsumerMap.put("mlKeyStoreAlgorithm", (config, prop) -> {
logger.info("REST and App-Services key store algorithm: " + prop);
config.setRestKeyStoreAlgorithm(prop);
config.setAppServicesKeyStoreAlgorithm(prop);
});
propertyConsumerMap.put("mlTrustStorePath", (config, prop) -> {
logger.info("REST and App-Services trust store path: " + prop);
config.setRestTrustStorePath(prop);
config.setAppServicesTrustStorePath(prop);
});
propertyConsumerMap.put("mlTrustStorePassword", (config, prop) -> {
config.setRestTrustStorePassword(prop);
config.setAppServicesTrustStorePassword(prop);
});
propertyConsumerMap.put("mlTrustStoreType", (config, prop) -> {
logger.info("REST and App-Services trust store type: " + prop);
config.setRestTrustStoreType(prop);
config.setAppServicesTrustStoreType(prop);
});
propertyConsumerMap.put("mlTrustStoreAlgorithm", (config, prop) -> {
logger.info("REST and App-Services trust store algorithm: " + prop);
config.setRestTrustStoreAlgorithm(prop);
config.setAppServicesTrustStoreAlgorithm(prop);
});
/**
* Defaults to port 8000. In rare cases, the ML App-Services app server will have been changed to listen on a
* different port, in which case you can set this to that port.
*/
propertyConsumerMap.put("mlAppServicesPort", (config, prop) -> {
logger.info("App services port: {}", prop);
config.setAppServicesPort(propertyToInteger("mlAppServicesPort", prop));
});
/**
* The username and password for a ML user with the rest-admin role that is used for e.g. loading
* non-REST API modules via the App Services client REST API, which is defined by the appServicesPort.
*/
propertyConsumerMap.put("mlAppServicesUsername", (config, prop) -> {
logger.info("App Services username: " + prop);
config.setAppServicesUsername(prop);
});
propertyConsumerMap.put("mlAppServicesPassword", (config, prop) -> {
config.setAppServicesPassword(prop);
});
propertyConsumerMap.put("mlAppServicesAuthentication", (config, prop) -> {
logger.info("App Services authentication: " + prop);
config.setAppServicesSecurityContextType(SecurityContextType.valueOf(prop.toUpperCase()));
});
propertyConsumerMap.put("mlAppServicesCertFile", (config, prop) -> {
logger.info("App Services certificate file: " + prop);
config.setAppServicesCertFile(prop);
});
propertyConsumerMap.put("mlAppServicesCertPassword", (config, prop) -> {
config.setAppServicesCertPassword(prop);
});
propertyConsumerMap.put("mlAppServicesConnectionType", (config, prop) -> {
logger.info("App Services connection type: " + prop);
config.setAppServicesConnectionType(DatabaseClient.ConnectionType.valueOf(prop));
});
propertyConsumerMap.put("mlAppServicesExternalName", (config, prop) -> {
logger.info("App Services external name: " + prop);
config.setAppServicesExternalName(prop);
});
propertyConsumerMap.put("mlAppServicesSamlToken", (config, prop) -> {
config.setAppServicesSamlToken(prop);
});
propertyConsumerMap.put("mlAppServicesSimpleSsl", (config, prop) -> {
if (StringUtils.hasText(prop) && !"false".equalsIgnoreCase(prop)) {
if ("true".equalsIgnoreCase(prop)) {
config.setAppServicesSimpleSslConfig();
} else {
config.setAppServicesSimpleSslConfig(prop);
}
String protocol = config.getAppServicesSslContext().getProtocol();
logger.info(format("Using protocol '%s' and 'ANY' hostname verifier for authenticating against the " +
"App-Services server", protocol));
}
});
propertyConsumerMap.put("mlAppServicesSslProtocol", (config, prop) -> {
logger.info("Using SSL protocol for App-Services server: " + prop);
config.setAppServicesSslProtocol(prop);
});
propertyConsumerMap.put("mlAppServicesSslHostnameVerifier", (config, prop) -> {
logger.info("App-Services SSL hostname verifier: " + prop);
config.setAppServicesSslHostnameVerifier(JavaClientUtil.toSSLHostnameVerifier(prop));
});
propertyConsumerMap.put("mlAppServicesUseDefaultKeystore", (config, prop) -> {
logger.info("Using default JVM keystore for SSL for App-Services server: " + prop);
config.setAppServicesUseDefaultKeystore(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlAppServicesTrustManagementAlgorithm", (config, prop) -> {
logger.info("Using trust management algorithm for SSL for App-Services server: " + prop);
config.setAppServicesTrustManagementAlgorithm(prop);
});
propertyConsumerMap.put("mlCloudBasePath", (config, prop) -> {
String defaultPath = prop + "/app-services";
logger.info("App-Services base path: " + defaultPath);
config.setAppServicesBasePath(defaultPath);
});
propertyConsumerMap.put("mlAppServicesBasePath", (config, prop) -> {
String cloudBasePath = getProperty("mlCloudBasePath");
String appServicesPath = StringUtils.hasText(cloudBasePath) ? cloudBasePath + prop : prop;
logger.info("App-Services base path: " + appServicesPath);
config.setAppServicesBasePath(appServicesPath);
});
propertyConsumerMap.put("mlAppServicesKeyStorePath", (config, prop) -> {
logger.info("App-Services key store path: " + prop);
config.setAppServicesKeyStorePath(prop);
});
propertyConsumerMap.put("mlAppServicesKeyStorePassword", (config, prop) -> {
config.setAppServicesKeyStorePassword(prop);
});
propertyConsumerMap.put("mlAppServicesKeyStoreType", (config, prop) -> {
logger.info("App-Services key store type: " + prop);
config.setAppServicesKeyStoreType(prop);
});
propertyConsumerMap.put("mlAppServicesKeyStoreAlgorithm", (config, prop) -> {
logger.info("App-Services key store algorithm: " + prop);
config.setAppServicesKeyStoreAlgorithm(prop);
});
propertyConsumerMap.put("mlAppServicesTrustStorePath", (config, prop) -> {
logger.info("App-Services trust store path: " + prop);
config.setAppServicesTrustStorePath(prop);
});
propertyConsumerMap.put("mlAppServicesTrustStorePassword", (config, prop) -> {
config.setAppServicesTrustStorePassword(prop);
});
propertyConsumerMap.put("mlAppServicesTrustStoreType", (config, prop) -> {
logger.info("App-Services trust store type: " + prop);
config.setAppServicesTrustStoreType(prop);
});
propertyConsumerMap.put("mlAppServicesTrustStoreAlgorithm", (config, prop) -> {
logger.info("App-Services trust store algorithm: " + prop);
config.setAppServicesTrustStoreAlgorithm(prop);
});
/**
* Set this to true to prevent creating a REST API server by default.
*/
propertyConsumerMap.put("mlNoRestServer", (config, prop) -> {
logger.info("Not creating REST server if no REST config file is found");
config.setNoRestServer(true);
});
/**
* If a REST API server is created, it will use the following port. Modules will also be loaded via this port.
*/
propertyConsumerMap.put("mlRestPort", (config, prop) -> {
logger.info("App REST port: " + prop);
config.setRestPort(propertyToInteger("mlRestPort", prop));
});
/**
* The username and password for a ML user with the rest-admin role. This user is used for operations against the
* Client REST API - namely, loading REST API modules such as options, services, and transforms.
*/
propertyConsumerMap.put("mlRestAdminUsername", (config, prop) -> {
logger.info("REST admin username: " + prop);
config.setRestAdminUsername(prop);
if (!propertyExists("mlAppServicesUsername")) {
logger.info("App Services username: " + prop);
config.setAppServicesUsername(prop);
}
});
propertyConsumerMap.put("mlRestAdminPassword", (config, prop) -> {
config.setRestAdminPassword(prop);
if (!propertyExists("mlAppServicesPassword")) {
config.setAppServicesPassword(prop);
}
});
propertyConsumerMap.put("mlRestConnectionType", (config, prop) -> {
logger.info("REST connection type: " + prop);
config.setRestConnectionType(DatabaseClient.ConnectionType.valueOf(prop));
});
propertyConsumerMap.put("mlRestAuthentication", (config, prop) -> {
logger.info("REST authentication: " + prop);
config.setRestSecurityContextType(SecurityContextType.valueOf(prop.toUpperCase()));
});
propertyConsumerMap.put("mlRestCertFile", (config, prop) -> {
logger.info("REST certificate file: " + prop);
config.setRestCertFile(prop);
});
propertyConsumerMap.put("mlRestCertPassword", (config, prop) -> {
config.setRestCertPassword(prop);
});
propertyConsumerMap.put("mlRestExternalName", (config, prop) -> {
logger.info("REST external name: " + prop);
config.setRestExternalName(prop);
});
propertyConsumerMap.put("mlRestSamlToken", (config, prop) -> {
config.setRestSamlToken(prop);
});
propertyConsumerMap.put("mlRestBasePath", (config, prop) -> {
String cloudBasePath = getProperty("mlCloudBasePath");
String restPath = StringUtils.hasText(cloudBasePath) ? cloudBasePath + prop : prop;
logger.info("REST base path: " + restPath);
config.setRestBasePath(restPath);
});
propertyConsumerMap.put("mlTestRestBasePath", (config, prop) -> {
String cloudBasePath = getProperty("mlCloudBasePath");
String testRestPath = StringUtils.hasText(cloudBasePath) ? cloudBasePath + prop : prop;
logger.info("Test REST base path: " + testRestPath);
config.setTestRestBasePath(testRestPath);
});
// Need this to be after mlRestAuthentication and mlAppServicesAuthentication are processed so
// that it doesn't override those values.
propertyConsumerMap.put("mlAuthentication", (config, prop) -> {
if (!propertyExists("mlAppServicesAuthentication")) {
logger.info("App Services authentication: " + prop);
config.setAppServicesSecurityContextType(SecurityContextType.valueOf(prop.toUpperCase()));
}
if (!propertyExists("mlRestAuthentication")) {
logger.info("REST authentication: " + prop);
config.setRestSecurityContextType(SecurityContextType.valueOf(prop.toUpperCase()));
}
});
propertyConsumerMap.put("mlSslHostnameVerifier", (config, prop) -> {
if (!propertyExists("mlAppServicesSslHostnameVerifier")) {
logger.info("App-Services SSL hostname verifier: " + prop);
config.setAppServicesSslHostnameVerifier(JavaClientUtil.toSSLHostnameVerifier(prop));
}
if (!propertyExists("mlRestSslHostnameVerifier")) {
logger.info("REST SSL hostname verifier: " + prop);
config.setRestSslHostnameVerifier(JavaClientUtil.toSSLHostnameVerifier(prop));
}
});
/**
* When modules are loaded via the Client REST API, if the app server requires an SSL connection, then
* setting this property will force the simplest SSL connection to be created.
*/
propertyConsumerMap.put("mlSimpleSsl", (config, prop) -> {
if (StringUtils.hasText(prop) && !"false".equalsIgnoreCase(prop)) {
if ("true".equalsIgnoreCase(prop)) {
config.setSimpleSslConfig();
} else {
config.setSimpleSslConfig(prop);
}
String protocol = config.getRestSslContext().getProtocol();
logger.info(format("Using protocol '%s' and 'ANY' hostname verifier for authenticating against the " +
"client REST API server", protocol));
}
});
propertyConsumerMap.put("mlRestSslProtocol", (config, prop) -> {
logger.info("Using SSL protocol for client REST API server: " + prop);
config.setRestSslProtocol(prop);
});
propertyConsumerMap.put("mlRestSslHostnameVerifier", (config, prop) -> {
logger.info("REST SSL hostname verifier: " + prop);
config.setRestSslHostnameVerifier(JavaClientUtil.toSSLHostnameVerifier(prop));
});
propertyConsumerMap.put("mlRestUseDefaultKeystore", (config, prop) -> {
logger.info("Using default JVM keystore for SSL for client REST API server: " + prop);
config.setRestUseDefaultKeystore(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlRestTrustManagementAlgorithm", (config, prop) -> {
logger.info("Using trust management algorithm for SSL for client REST API server: " + prop);
config.setRestTrustManagementAlgorithm(prop);
});
propertyConsumerMap.put("mlRestKeyStorePath", (config, prop) -> {
logger.info("REST key store path: " + prop);
config.setRestKeyStorePath(prop);
});
propertyConsumerMap.put("mlRestKeyStorePassword", (config, prop) -> {
config.setRestKeyStorePassword(prop);
});
propertyConsumerMap.put("mlRestKeyStoreType", (config, prop) -> {
logger.info("REST key store type: " + prop);
config.setRestKeyStoreType(prop);
});
propertyConsumerMap.put("mlRestKeyStoreAlgorithm", (config, prop) -> {
logger.info("REST key store algorithm: " + prop);
config.setRestKeyStoreAlgorithm(prop);
});
propertyConsumerMap.put("mlRestTrustStorePath", (config, prop) -> {
logger.info("REST trust store path: " + prop);
config.setRestTrustStorePath(prop);
});
propertyConsumerMap.put("mlRestTrustStorePassword", (config, prop) -> {
config.setRestTrustStorePassword(prop);
});
propertyConsumerMap.put("mlRestTrustStoreType", (config, prop) -> {
logger.info("REST trust store type: " + prop);
config.setRestTrustStoreType(prop);
});
propertyConsumerMap.put("mlRestTrustStoreAlgorithm", (config, prop) -> {
logger.info("REST trust store algorithm: " + prop);
config.setRestTrustStoreAlgorithm(prop);
});
/**
* mlUsername and mlPassword are the default username/password for connecting to the app's REST server (if one
* exists) and to App-Services on 8000. These are processed before the other username/password properties so that
* the other ones will override what these set.
*/
propertyConsumerMap.put("mlUsername", (config, prop) -> {
if (!propertyExists("mlRestAdminUsername")) {
logger.info("REST admin username: " + prop);
config.setRestAdminUsername(prop);
}
if (!propertyExists("mlAppServicesUsername")) {
logger.info("App Services username: " + prop);
config.setAppServicesUsername(prop);
}
});
propertyConsumerMap.put("mlPassword", (config, prop) -> {
if (!propertyExists("mlRestAdminPassword")) {
config.setRestAdminPassword(prop);
}
if (!propertyExists("mlAppServicesPassword")) {
config.setAppServicesPassword(prop);
}
});
propertyConsumerMap.put("mlRestServerName", (config, prop) -> {
logger.info("REST server name: " + prop);
config.setRestServerName(prop);
});
/**
* If a test REST API server is created, it will use the following port.
*/
propertyConsumerMap.put("mlTestRestPort", (config, prop) -> {
logger.info("Test REST port: " + prop);
config.setTestRestPort(propertyToInteger("mlTestRestPort", prop));
});
propertyConsumerMap.put("mlTestRestServerName", (config, prop) -> {
logger.info("Test REST server name: " + prop);
config.setTestRestServerName(prop);
});
propertyConsumerMap.put("mlTestContentDatabaseName", (config, prop) -> {
logger.info("Test content database name: " + prop);
config.setTestContentDatabaseName(prop);
});
// Deprecated - use mlSchemaPaths instead
propertyConsumerMap.put("mlSchemasPath", (config, prop) -> {
logger.info("mlSchemasPath is deprecated as of version 3.13.0; please use mlSchemaPaths instead; schemas path: " + prop);
config.setSchemaPaths(buildPathListFromCommaDelimitedString(prop));
});
propertyConsumerMap.put("mlSchemaPaths", (config, prop) -> {
logger.info("Schema paths: " + prop);
config.setSchemaPaths(buildPathListFromCommaDelimitedString(prop));
});
propertyConsumerMap.put("mlTdeValidationEnabled", (config, prop) -> {
logger.info("TDE validation enabled: " + prop);
config.setTdeValidationEnabled(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlSchemasDatabaseName", (config, prop) -> {
logger.info("Schemas database name: " + prop);
config.setSchemasDatabaseName(prop);
});
propertyConsumerMap.put("mlTriggersDatabaseName", (config, prop) -> {
logger.info("Triggers database name: " + prop);
config.setTriggersDatabaseName(prop);
});
propertyConsumerMap.put("mlCpfDatabaseName", (config, prop) -> {
logger.info("CPF database name: " + prop);
config.setCpfDatabaseName(prop);
});
propertyConsumerMap.put("mlContentForestsPerHost", (config, prop) -> {
logger.info("Content forests per host: " + prop);
config.setContentForestsPerHost(propertyToInteger("mlContentForestsPerHost", prop));
});
propertyConsumerMap.put("mlCreateForests", (config, prop) -> {
logger.info("Create forests for each deployed database: " + prop);
config.setCreateForests(Boolean.parseBoolean(prop));
});
/**
* For any database besides the content database, configure the number of forests per host.
*/
propertyConsumerMap.put("mlForestsPerHost", (config, prop) -> {
logger.info("Forests per host: " + prop);
String[] tokens = prop.split(",");
for (int i = 0; i < tokens.length; i += 2) {
config.getForestCounts().put(tokens[i], propertyToInteger("mlForestsPerHost", tokens[i + 1]));
}
});
/**
* This property can specify a comma-delimited list of database names and replica counts as a simple way of
* setting up forest replicas - e.g. Documents,1,Security,2.
*/
propertyConsumerMap.put("mlDatabaseNamesAndReplicaCounts", (config, prop) -> {
logger.info("Database names and replica counts: " + prop);
String[] tokens = prop.split(",");
Map map = new HashMap<>();
for (int i = 0; i < tokens.length; i += 2) {
map.put(tokens[i], propertyToInteger("mlDatabaseNamesAndReplicaCounts", tokens[i + 1]));
}
config.setDatabaseNamesAndReplicaCounts(map);
});
propertyConsumerMap.put("mlDatabasesWithForestsOnOneHost", (config, prop) -> {
logger.info("Databases that will have their forest(s) created on a single host: " + prop);
String[] names = prop.split(",");
Set set = new HashSet<>();
set.addAll(Arrays.asList(names));
config.setDatabasesWithForestsOnOneHost(set);
});
propertyConsumerMap.put("mlDatabaseGroups", (config, prop) -> {
logger.info("Databases and the groups containing the hosts that their forests will be created on: " + prop);
config.setDatabaseGroups(buildMapOfListsFromDelimitedString(prop));
});
propertyConsumerMap.put("mlHostGroups", (config, prop) -> {
logger.info("Hosts will be assigned to groups: " + prop);
config.setHostGroups(buildMapFromCommaDelimitedString(prop));
});
propertyConsumerMap.put("mlDatabaseHosts", (config, prop) -> {
logger.info("Databases and the hosts that their forests will be created on: " + prop);
config.setDatabaseHosts(buildMapOfListsFromDelimitedString(prop));
});
propertyConsumerMap.put("mlForestDataDirectory", (config, prop) -> {
logger.info("Default forest data directory for all databases: " + prop);
config.setForestDataDirectory(prop);
});
propertyConsumerMap.put("mlForestFastDataDirectory", (config, prop) -> {
logger.info("Default forest fast data directory for all databases: " + prop);
config.setForestFastDataDirectory(prop);
});
propertyConsumerMap.put("mlForestLargeDataDirectory", (config, prop) -> {
logger.info("Default forest large data directory for all databases: " + prop);
config.setForestLargeDataDirectory(prop);
});
propertyConsumerMap.put("mlReplicaForestDataDirectory", (config, prop) -> {
logger.info("Default replica forest data directory for all databases: " + prop);
config.setReplicaForestDataDirectory(prop);
});
propertyConsumerMap.put("mlReplicaForestLargeDataDirectory", (config, prop) -> {
logger.info("Default replica forest large data directory for all databases: " + prop);
config.setReplicaForestLargeDataDirectory(prop);
});
propertyConsumerMap.put("mlReplicaForestFastDataDirectory", (config, prop) -> {
logger.info("Default replica forest fast data directory for all databases: " + prop);
config.setReplicaForestFastDataDirectory(prop);
});
propertyConsumerMap.put("mlDatabaseDataDirectories", (config, prop) -> {
logger.info("Databases and forest data directories: " + prop);
config.setDatabaseDataDirectories(buildMapOfListsFromDelimitedString(prop));
});
propertyConsumerMap.put("mlDatabaseFastDataDirectories", (config, prop) -> {
logger.info("Databases and forest fast data directories: " + prop);
config.setDatabaseFastDataDirectories(buildMapFromCommaDelimitedString(prop));
});
propertyConsumerMap.put("mlDatabaseLargeDataDirectories", (config, prop) -> {
logger.info("Databases and forest large data directories: " + prop);
config.setDatabaseLargeDataDirectories(buildMapFromCommaDelimitedString(prop));
});
propertyConsumerMap.put("mlDatabaseReplicaDataDirectories", (config, prop) -> {
logger.info("Databases and replica forest data directories: " + prop);
config.setDatabaseReplicaDataDirectories(buildMapOfListsFromDelimitedString(prop));
});
propertyConsumerMap.put("mlDatabaseReplicaFastDataDirectories", (config, prop) -> {
logger.info("Databases and replica forest fast data directories: " + prop);
config.setDatabaseReplicaFastDataDirectories(buildMapFromCommaDelimitedString(prop));
});
propertyConsumerMap.put("mlDatabaseReplicaLargeDataDirectories", (config, prop) -> {
logger.info("Databases and replica forest large data directories: " + prop);
config.setDatabaseReplicaLargeDataDirectories(buildMapFromCommaDelimitedString(prop));
});
/**
* When undo is invoked on DeployDatabaseCommand (such as via mlUndeploy in ml-gradle), this controls whether
* or not forests are deleted, or just their configuration is deleted. If mlDeleteReplicas is set to true, this
* has no impact - currently, the forests and their replicas will be deleted for efficiency reasons (results in
* fewer calls to the Management REST API.
*/
propertyConsumerMap.put("mlDeleteForests", (config, prop) -> {
logger.info("Delete forests when a database is deleted: " + prop);
config.setDeleteForests(Boolean.parseBoolean(prop));
});
/**
* When undo is invoked on DeployDatabaseCommand (such as via mlUndeploy in ml-gradle), this controls whether
* primary forests and their replicas are deleted first. Most of the time, you want this set to true
* (the default) as otherwise, the database can't be deleted and the Management REST API will throw an error.
*/
propertyConsumerMap.put("mlDeleteReplicas", (config, prop) -> {
logger.info("Delete replicas when a database is deleted: " + prop);
config.setDeleteReplicas(Boolean.parseBoolean(prop));
});
/**
* When a REST API server is created, the content database name will default to mlAppName-content. This property
* can be used to override that name.
*/
propertyConsumerMap.put("mlContentDatabaseName", (config, prop) -> {
logger.info("Content database name: " + prop);
config.setContentDatabaseName(prop);
});
/**
* When a REST API server is created, the modules database name will default to mlAppName-modules. This property
* can be used to override that name.
*/
propertyConsumerMap.put("mlModulesDatabaseName", (config, prop) -> {
logger.info("Modules database name: " + prop);
config.setModulesDatabaseName(prop);
});
/**
* Specifies the path for flexrep configuration files; used by DeployFlexrepCommand.
*/
propertyConsumerMap.put("mlFlexrepPath", (config, prop) -> {
logger.info("Flexrep path: " + prop);
config.setFlexrepPath(prop);
});
/**
* "Default" is the assumed group for group-specific resources, such as app servers and scheduled tasks. This
* property can be set to override that.
*/
propertyConsumerMap.put("mlGroupName", (config, prop) -> {
logger.info("Group name: " + prop);
config.setGroupName(prop);
});
/**
* When modules are loaded via the Client REST API, this property can specify a comma-delimited set of role/capability
* permissions - e.g. rest-reader,read,rest-writer,update.
*/
propertyConsumerMap.put("mlModulePermissions", (config, prop) -> {
logger.info("Module permissions: " + prop);
config.setModulePermissions(prop);
});
/**
* When modules are loaded via the Client REST API, this property can specify a comma-delimited set of extensions
* for files that should be loaded as binaries.
*/
propertyConsumerMap.put("mlAdditionalBinaryExtensions", (config, prop) -> {
String[] values = prop.split(",");
logger.info("Additional binary extensions for loading modules: " + Arrays.asList(values));
config.setAdditionalBinaryExtensions(values);
});
/**
* By default, tokens in module files will be replaced. This property can be used to enable/disable that behavior.
*/
propertyConsumerMap.put("mlReplaceTokensInModules", (config, prop) -> {
logger.info("Replace tokens in modules: " + prop);
config.setReplaceTokensInModules(Boolean.parseBoolean(prop));
});
/**
* To mimic Roxy behavior, tokens in modules are expected to start with "@ml.". If you do not want this behavior,
* you can set this property to false to disable it.
*/
propertyConsumerMap.put("mlUseRoxyTokenPrefix", (config, prop) -> {
logger.info("Use Roxy token prefix of '@ml.': " + prop);
config.setUseRoxyTokenPrefix(Boolean.parseBoolean(prop));
});
/**
* Comma-separated list of paths for loading modules. Defaults to src/main/ml-modules.
*/
propertyConsumerMap.put("mlModulePaths", (config, prop) -> {
logger.info("Module paths: " + prop);
config.setModulePaths(buildPathListFromCommaDelimitedString(prop));
});
propertyConsumerMap.put("mlModuleTimestampsPath", (config, prop) -> {
if (prop.trim().length() == 0) {
logger.info("Disabling use of module timestamps file");
config.setModuleTimestampsPath(null);
} else {
logger.info("Module timestamps path: " + prop);
config.setModuleTimestampsPath(prop);
}
});
propertyConsumerMap.put("mlModuleTimestampsUseHost", (config, prop) -> {
logger.info("Use host in module timestamps file: " + prop);
config.setModuleTimestampsUseHost(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlModuleUriPrefix", (config, prop) -> {
logger.info("Will added prefix to the URI of each module: " + prop);
config.setModuleUriPrefix(prop);
});
propertyConsumerMap.put("mlModulesRegex", (config, prop) -> {
logger.info("Including module filenames matching regex: " + prop);
config.setModuleFilenamesIncludePattern(Pattern.compile(prop));
});
/**
* Whether or not to load asset modules in bulk - i.e. in one transaction. Defaults to true.
*/
propertyConsumerMap.put("mlBulkLoadAssets", (config, prop) -> {
logger.info("Bulk load modules: " + prop);
config.setBulkLoadAssets(Boolean.parseBoolean(prop));
});
/**
* Whether or not to statically check asset modules after they're loaded - defaults to false.
*/
propertyConsumerMap.put("mlStaticCheckAssets", (config, prop) -> {
logger.info("Statically check asset modules: " + prop);
config.setStaticCheckAssets(Boolean.parseBoolean(prop));
});
/**
* Whether or not to attempt to statically check asset library modules after they're loaded - defaults to false.
* If mlStaticCheckAssets is true and this is false, and no errors will be thrown for library modules.
* See XccAssetLoader in ml-javaclient-util for information on how this tries to check a library module.
*/
propertyConsumerMap.put("mlStaticCheckLibraryAssets", (config, prop) -> {
logger.info("Statically check asset library modules: " + prop);
config.setStaticCheckLibraryAssets(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeleteTestModules", (config, prop) -> {
logger.info("Delete test modules: " + prop);
config.setDeleteTestModules(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDeleteTestModulesPattern", (config, prop) -> {
logger.info("Delete test modules pattern: " + prop);
config.setDeleteTestModulesPattern(prop);
});
propertyConsumerMap.put("mlModulesLoaderThreadCount", (config, prop) -> {
logger.info("Modules loader thread count: " + prop);
config.setModulesLoaderThreadCount(propertyToInteger("mlModulesLoaderThreadCount", prop));
});
propertyConsumerMap.put("mlModulesLoaderBatchSize", (config, prop) -> {
logger.info("Modules loader batch size: " + prop);
config.setModulesLoaderBatchSize(propertyToInteger("mlModulesLoaderBatchSize", prop));
});
propertyConsumerMap.put("mlCascadeCollections", (config, prop) -> {
logger.info("Cascade collections.properties configuration when loading data, modules, and schemas: " + prop);
config.setCascadeCollections(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlCascadePermissions", (config, prop) -> {
logger.info("Cascade permissions.properties configuration when loading data, modules, and schemas: " + prop);
config.setCascadePermissions(Boolean.parseBoolean(prop));
});
/**
* The following properties are all for generating Entity Services artifacts.
*/
propertyConsumerMap.put("mlModelsDatabase", (config, prop) -> {
logger.info("Entity Services models database: " + prop);
config.setModelsDatabase(prop);
});
propertyConsumerMap.put("mlModelsPath", (config, prop) -> {
logger.info("Entity Services models path: " + prop);
config.setModelsPath(prop);
});
propertyConsumerMap.put("mlInstanceConverterPath", (config, prop) -> {
logger.info("Entity Services instance converter path: " + prop);
config.setInstanceConverterPath(prop);
});
propertyConsumerMap.put("mlGenerateInstanceConverter", (config, prop) -> {
logger.info("Entity Services generate instance converter: " + prop);
config.setGenerateInstanceConverter(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlGenerateSchema", (config, prop) -> {
logger.info("Entity Services generate schema: " + prop);
config.setGenerateSchema(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlGenerateSearchOptions", (config, prop) -> {
logger.info("Entity Services generate search options: " + prop);
config.setGenerateSearchOptions(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlGenerateDatabaseProperties", (config, prop) -> {
logger.info("Entity Services generate database properties: " + prop);
config.setGenerateDatabaseProperties(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlGenerateExtractionTemplate", (config, prop) -> {
logger.info("Entity Services generate extraction template: " + prop);
config.setGenerateExtractionTemplate(Boolean.parseBoolean(prop));
});
// End Entity Services properties
/**
* Sets resource filenames to ignore on ALL commands. Be careful here, in case you have files for different kinds
* of resources, but with the same filename (this should be very rare and easily avoided).
*
* Also that as of version 2.6.0 of ml-app-deployer, this property is processed by AbstractAppDeployer, NOT by
* the Command itself. So in order for this property to be applied, you must execute a Command via a subclass of
* AbstractAppDeployer (most commonly SimpleAppDeployer).
*/
propertyConsumerMap.put("mlResourceFilenamesToIgnore", (config, prop) -> {
String[] values = prop.split(",");
logger.info("Ignoring resource filenames: " + Arrays.asList(values));
config.setResourceFilenamesToIgnore(values);
});
propertyConsumerMap.put("mlResourceFilenamesToExcludeRegex", (config, prop) -> {
logger.info("Excluding resource filenames matching regex: " + prop);
config.setResourceFilenamesExcludePattern(Pattern.compile(prop));
});
propertyConsumerMap.put("mlResourceFilenamesToIncludeRegex", (config, prop) -> {
logger.info("Including resource filenames matching regex: " + prop);
config.setResourceFilenamesIncludePattern(Pattern.compile(prop));
});
propertyConsumerMap.put("mlExcludeProperties", (config, prop) -> {
String[] values = prop.split(",");
logger.info("Will exclude these properties from all resource payloads: " + Arrays.asList(values));
config.setExcludeProperties(values);
});
propertyConsumerMap.put("mlIncludeProperties", (config, prop) -> {
String[] values = prop.split(",");
logger.info("Will include only these properties in all resource payloads: " + Arrays.asList(values));
config.setIncludeProperties(values);
});
propertyConsumerMap.put("mlIncremental", (config, prop) -> {
logger.info("Supported resources will only be deployed if their resource files are new or have been modified since the last deployment: " + prop);
config.setIncrementalDeploy(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlUpdateMimetypeWhenPropertiesAreEqual", (config, prop) -> {
logger.info("Update mimetype when properties are equal (defaults to false to avoid unnecessary ML restarts): " + prop);
config.setUpdateMimetypeWhenPropertiesAreEqual(Boolean.parseBoolean(prop));
});
registerDataLoadingProperties();
registerPluginProperties();
}
protected void registerDataLoadingProperties() {
propertyConsumerMap.put("mlDataBatchSize", (config, prop) -> {
logger.info("Batch size for loading data: " + prop);
config.getDataConfig().setBatchSize(propertyToInteger("mlDataBatchSize", prop));
});
propertyConsumerMap.put("mlDataCollections", (config, prop) -> {
logger.info("Collections that data will be loaded into: " + prop);
config.getDataConfig().setCollections(prop.split(","));
});
propertyConsumerMap.put("mlDataDatabaseName", (config, prop) -> {
logger.info("Database that data will be loaded into: " + prop);
config.getDataConfig().setDatabaseName(prop);
});
propertyConsumerMap.put("mlDataPaths", (config, prop) -> {
logger.info("Paths that data will be loaded from: " + prop);
List paths = new ArrayList<>();
for (String s : prop.split(",")) {
String path = this.projectDir != null ? new File(projectDir, s).getAbsolutePath() : s;
paths.add(path);
}
config.getDataConfig().setDataPaths(paths);
});
propertyConsumerMap.put("mlDataLoadingEnabled", (config, prop) -> {
logger.info("Whether data loading is enabled: " + prop);
config.getDataConfig().setDataLoadingEnabled(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDataLogUris", (config, prop) -> {
logger.info("Log URIs when loading data: " + prop);
config.getDataConfig().setLogUris(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlDataPermissions", (config, prop) -> {
logger.info("Permissions to be applied to loaded data: " + prop);
config.getDataConfig().setPermissions(prop);
});
propertyConsumerMap.put("mlDataReplaceTokens", (config, prop) -> {
logger.info("Whether tokens will be replaced when loading data: " + prop);
config.getDataConfig().setReplaceTokensInData(Boolean.parseBoolean(prop));
});
}
protected void registerPluginProperties() {
propertyConsumerMap.put("mlPluginDatabaseName", (config, prop) -> {
logger.info("Database that plugins will be loaded into and installed from: " + prop);
config.getPluginConfig().setDatabaseName(prop);
});
propertyConsumerMap.put("mlPluginInstallationEnabled", (config, prop) -> {
logger.info("Whether plugins will be installed: " + prop);
config.getPluginConfig().setEnabled(Boolean.parseBoolean(prop));
});
propertyConsumerMap.put("mlPluginPaths", (config, prop) -> {
logger.info("Paths that plugins will be installed from: " + prop);
config.getPluginConfig().setPluginPaths(buildPathListFromCommaDelimitedString(prop));
});
propertyConsumerMap.put("mlPluginUriPrefix", (config, prop) -> {
logger.info("URI prefix for plugins: " + prop);
config.getPluginConfig().setUriPrefix(prop);
});
}
protected ConfigDir buildConfigDir(String path) {
File baseDir = this.projectDir != null ? new File(this.projectDir, path) : new File(path);
return new ConfigDir(baseDir);
}
protected List buildPathListFromCommaDelimitedString(String prop) {
String[] paths = prop.split(",");
List list = new ArrayList<>();
for (String s : paths) {
String path = this.projectDir != null ? new File(projectDir, s).getAbsolutePath() : s;
list.add(path);
}
return list;
}
protected Map buildMapFromCommaDelimitedString(String str) {
Map map = new HashMap<>();
String[] tokens = str.split(",");
for (int i = 0; i < tokens.length; i += 2) {
map.put(tokens[i], tokens[i + 1]);
}
return map;
}
protected Map> buildMapOfListsFromDelimitedString(String str) {
String[] tokens = str.split(",");
Map> map = new LinkedHashMap<>();
for (int i = 0; i < tokens.length; i += 2) {
String dbName = tokens[i];
String[] hostNames = tokens[i + 1].split("\\|");
List names = new ArrayList<>();
names.addAll(Arrays.asList(hostNames));
map.put(dbName, names);
}
return map;
}
/**
* This is provided so that a client can easily print out a list of all the supported properties.
*
* @return
*/
public Map> getPropertyConsumerMap() {
return propertyConsumerMap;
}
public void setProjectDir(File projectDir) {
this.projectDir = projectDir;
}
}