/*
* (C) Copyright 2010-2017 Nuxeo (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Julien Carsique
*/
package org.nuxeo.launcher.config;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.NullEnumeration;
import org.nuxeo.common.Environment;
import org.nuxeo.common.codec.Crypto;
import org.nuxeo.common.codec.CryptoProperties;
import org.nuxeo.common.utils.TextTemplate;
import org.nuxeo.launcher.commons.DatabaseDriverException;
import org.nuxeo.launcher.config.JVMVersion.UpTo;
import org.nuxeo.log4j.Log4JHelper;
import freemarker.core.ParseException;
import freemarker.template.TemplateException;
/**
* Builder for server configuration and datasource files from templates and properties.
*
* @author jcarsique
*/
public class ConfigurationGenerator {
/**
* @since 6.0
*/
public static final String TEMPLATE_SEPARATOR = ",";
/**
* Accurate but not used internally. NXP-18023: Java 8 update 40+ required
*
* @since 5.7
*/
public static final String[] COMPLIANT_JAVA_VERSIONS = new String[] { "1.8.0_40" };
/**
* @since 5.6
*/
protected static final String CONFIGURATION_PROPERTIES = "configuration.properties";
private static final Log log = LogFactory.getLog(ConfigurationGenerator.class);
public static final String NUXEO_CONF = "nuxeo.conf";
public static final String TEMPLATES = "templates";
public static final String NUXEO_DEFAULT_CONF = "nuxeo.defaults";
/**
* Absolute or relative PATH to the user chosen templates (comma separated list)
*/
public static final String PARAM_TEMPLATES_NAME = "nuxeo.templates";
public static final String PARAM_TEMPLATE_DBNAME = "nuxeo.dbtemplate";
/**
* @since 8.1
*/
public static final String PARAM_TEMPLATE_DBNOSQL_NAME = "nuxeo.dbnosqltemplate";
public static final String PARAM_TEMPLATE_DBTYPE = "nuxeo.db.type";
/**
* @since 8.1
*/
public static final String PARAM_TEMPLATE_DBNOSQL_TYPE = "nuxeo.dbnosql.type";
public static final String OLD_PARAM_TEMPLATES_PARSING_EXTENSIONS = "nuxeo.templates.parsing.extensions";
public static final String PARAM_TEMPLATES_PARSING_EXTENSIONS = "nuxeo.plaintext_parsing_extensions";
public static final String PARAM_TEMPLATES_FREEMARKER_EXTENSIONS = "nuxeo.freemarker_parsing_extensions";
/**
* Absolute or relative PATH to the included templates (comma separated list)
*/
protected static final String PARAM_INCLUDED_TEMPLATES = "nuxeo.template.includes";
public static final String PARAM_FORCE_GENERATION = "nuxeo.force.generation";
public static final String BOUNDARY_BEGIN = "### BEGIN - DO NOT EDIT BETWEEN BEGIN AND END ###";
public static final String BOUNDARY_END = "### END - DO NOT EDIT BETWEEN BEGIN AND END ###";
public static final List<String> DB_LIST = Arrays.asList("default", "postgresql", "oracle", "mysql", "mariadb",
"mssql", "db2");
public static final List<String> DB_NOSQL_LIST = Arrays.asList("none", "mongodb", "marklogic");
public static final List<String> DB_EXCLUDE_CHECK_LIST = Arrays.asList("default", "none");
public static final String PARAM_WIZARD_DONE = "nuxeo.wizard.done";
public static final String PARAM_WIZARD_RESTART_PARAMS = "wizard.restart.params";
public static final String PARAM_FAKE_WINDOWS = "org.nuxeo.fake.vindoz";
public static final String PARAM_LOOPBACK_URL = "nuxeo.loopback.url";
public static final int MIN_PORT = 1;
public static final int MAX_PORT = 65535;
public static final int ADDRESS_PING_TIMEOUT = 1000;
public static final String PARAM_BIND_ADDRESS = "nuxeo.bind.address";
public static final String PARAM_HTTP_PORT = "nuxeo.server.http.port";
/**
* @deprecated Since 7.4. Use {@link Environment#SERVER_STATUS_KEY} instead
*/
@Deprecated
public static final String PARAM_STATUS_KEY = Environment.SERVER_STATUS_KEY;
public static final String PARAM_CONTEXT_PATH = "org.nuxeo.ecm.contextPath";
public static final String PARAM_MP_DIR = "nuxeo.distribution.marketplace.dir";
public static final String DISTRIBUTION_MP_DIR = "setupWizardDownloads";
public static final String INSTALL_AFTER_RESTART = "installAfterRestart.log";
public static final String PARAM_DB_DRIVER = "nuxeo.db.driver";
public static final String PARAM_DB_JDBC_URL = "nuxeo.db.jdbc.url";
public static final String PARAM_DB_HOST = "nuxeo.db.host";
public static final String PARAM_DB_PORT = "nuxeo.db.port";
public static final String PARAM_DB_NAME = "nuxeo.db.name";
public static final String PARAM_DB_USER = "nuxeo.db.user";
public static final String PARAM_DB_PWD = "nuxeo.db.password";
/**
* @since 8.1
*/
public static final String PARAM_MONGODB_NAME = "nuxeo.mongodb.dbname";
/**
* @since 8.1
*/
public static final String PARAM_MONGODB_SERVER = "nuxeo.mongodb.server";
/**
* Catch values like ${env:PARAM_KEY:defaultValue}
*
* @since 9.1
*/
private static final Pattern ENV_VALUE_PATTERN = Pattern.compile(
"\\$\\{env(?<boolean>\\?\\?)?:(?<envparam>\\w*)(:?(?<defaultvalue>.*?)?)?\\}");
/**
* Keys which value must be displayed thoughtfully
*
* @since 8.1
*/
public static final List<String> SECRET_KEYS = Arrays.asList(PARAM_DB_PWD, "mailservice.password",
"mail.transport.password", "nuxeo.http.proxy.password", "nuxeo.ldap.bindpassword",
"nuxeo.user.emergency.password");
/**
* @deprecated Since 7.10. Use {@link Environment#PRODUCT_NAME}
*/
@Deprecated
public static final String PARAM_PRODUCT_NAME = Environment.PRODUCT_NAME;
/**
* @deprecated Since 7.10. Use {@link Environment#PRODUCT_VERSION}
*/
@Deprecated
public static final String PARAM_PRODUCT_VERSION = Environment.PRODUCT_VERSION;
/**
* @since 5.6
*/
public static final String PARAM_NUXEO_URL = "nuxeo.url";
/**
* Global dev property, duplicated from runtime framework
*
* @since 5.6
*/
public static final String NUXEO_DEV_SYSTEM_PROP = "org.nuxeo.dev";
/**
* Seam hot reload property, also controlled by {@link #NUXEO_DEV_SYSTEM_PROP}
*
* @since 5.6
*/
public static final String SEAM_DEBUG_SYSTEM_PROP = "org.nuxeo.seam.debug";
/** @since 8.4 */
public static final String JVMCHECK_PROP = "jvmcheck";
/** @since 8.4 */
public static final String JVMCHECK_FAIL = "fail";
/** @since 8.4 */
public static final String JVMCHECK_NOFAIL = "nofail";
private final File nuxeoHome;
// User configuration file
private final File nuxeoConf;
// Chosen templates
private final List<File> includedTemplates = new ArrayList<>();
// Common default configuration file
private File nuxeoDefaultConf;
public boolean isJBoss;
public boolean isJetty;
public boolean isTomcat;
private ServerConfigurator serverConfigurator;
private BackingServiceConfigurator backingServicesConfigurator;
private boolean forceGeneration;
private Properties defaultConfig;
private CryptoProperties userConfig;
private boolean configurable = false;
private boolean onceGeneration = false;
private String templates;
// if PARAM_FORCE_GENERATION=once, set to false; else keep current value
private boolean setOnceToFalse = true;
// if PARAM_FORCE_GENERATION=false, set to once; else keep the current
// value
private boolean setFalseToOnce = false;
public boolean isConfigurable() {
return configurable;
}
public ConfigurationGenerator() {
this(true, false);
}
private boolean quiet = false;
private static boolean hideDeprecationWarnings = false;
private Environment env;
private Properties storedConfig;
private String currentConfigurationDigest;
/**
* @since 5.7
*/
protected Properties getStoredConfig() {
if (storedConfig == null) {
updateStoredConfig();
}
return storedConfig;
}
protected static final Map<String, String> parametersMigration = new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
{
put(OLD_PARAM_TEMPLATES_PARSING_EXTENSIONS, PARAM_TEMPLATES_PARSING_EXTENSIONS);
put("nuxeo.db.user.separator.key", "nuxeo.db.user_separator_key");
put("mail.pop3.host", "mail.store.host");
put("mail.pop3.port", "mail.store.port");
put("mail.smtp.host", "mail.transport.host");
put("mail.smtp.port", "mail.transport.port");
put("mail.smtp.username", "mail.transport.username");
put("mail.transport.username", "mail.transport.user");
put("mail.smtp.password", "mail.transport.password");
put("mail.smtp.usetls", "mail.transport.usetls");
put("mail.smtp.auth", "mail.transport.auth");
}
};
/**
* @param quiet Suppress info level messages from the console output
* @param debug Activate debug level logging
* @since 5.6
*/
public ConfigurationGenerator(boolean quiet, boolean debug) {
this.quiet = quiet;
File serverHome = Environment.getDefault().getServerHome();
if (serverHome != null) {
nuxeoHome = serverHome.getAbsoluteFile();
} else {
File userDir = new File(System.getProperty("user.dir"));
if ("bin".equalsIgnoreCase(userDir.getName())) {
nuxeoHome = userDir.getParentFile().getAbsoluteFile();
} else {
nuxeoHome = userDir.getAbsoluteFile();
}
}
String nuxeoConfPath = System.getProperty(NUXEO_CONF);
if (nuxeoConfPath != null) {
nuxeoConf = new File(nuxeoConfPath).getAbsoluteFile();
} else {
nuxeoConf = new File(nuxeoHome, "bin" + File.separator + "nuxeo.conf").getAbsoluteFile();
}
System.setProperty(NUXEO_CONF, nuxeoConf.getPath());
nuxeoDefaultConf = new File(nuxeoHome, TEMPLATES + File.separator + NUXEO_DEFAULT_CONF);
// detect server type based on System properties
isJetty = System.getProperty(JettyConfigurator.JETTY_HOME) != null;
isTomcat = System.getProperty(TomcatConfigurator.TOMCAT_HOME) != null;
if (!isJBoss && !isJetty && !isTomcat) {
// fallback on jar detection
isJBoss = new File(nuxeoHome, "bin/run.jar").exists();
isTomcat = new File(nuxeoHome, "bin/bootstrap.jar").exists();
String[] files = nuxeoHome.list();
for (String file : files) {
if (file.startsWith("nuxeo-runtime-launcher")) {
isJetty = true;
break;
}
}
}
if (isTomcat) {
serverConfigurator = new TomcatConfigurator(this);
} else if (isJetty) {
serverConfigurator = new JettyConfigurator(this);
} else {
serverConfigurator = new UnknownServerConfigurator(this);
}
if (Logger.getRootLogger().getAllAppenders() instanceof NullEnumeration) {
serverConfigurator.initLogs();
}
backingServicesConfigurator = new BackingServiceConfigurator(this);
String homeInfo = "Nuxeo home: " + nuxeoHome.getPath();
String confInfo = "Nuxeo configuration: " + nuxeoConf.getPath();
if (quiet) {
log.debug(homeInfo);
log.debug(confInfo);
} else {
log.info(homeInfo);
log.info(confInfo);
}
}
public void hideDeprecationWarnings(boolean hide) {
hideDeprecationWarnings = hide;
}
/**
* @see #PARAM_FORCE_GENERATION
*/
public void setForceGeneration(boolean forceGeneration) {
this.forceGeneration = forceGeneration;
}
/**
* @see #PARAM_FORCE_GENERATION
* @return true if configuration will be generated from templates
* @since 5.4.2
*/
public boolean isForceGeneration() {
return forceGeneration;
}
public CryptoProperties getUserConfig() {
return userConfig;
}
/**
* @since 5.4.2
*/
public final ServerConfigurator getServerConfigurator() {
return serverConfigurator;
}
/**
* Runs the configuration files generation.
*/
public void run() throws ConfigurationException {
if (init()) {
if (!serverConfigurator.isConfigured()) {
log.info("No current configuration, generating files...");
generateFiles();
} else if (forceGeneration) {
log.info("Configuration files generation (nuxeo.force.generation="
+ userConfig.getProperty(PARAM_FORCE_GENERATION) + ")...");
generateFiles();
} else {
log.info(
"Server already configured (set nuxeo.force.generation=true to force configuration files generation).");
}
}
}
/**
* Initialize configurator, check requirements and load current configuration
*
* @return returns true if current install is configurable, else returns false
*/
public boolean init() {
return init(false);
}
/**
* Initialize configurator, check requirements and load current configuration
*
* @since 5.6
* @param forceReload If true, forces configuration reload.
* @return returns true if current install is configurable, else returns false
*/
public boolean init(boolean forceReload) {
if (serverConfigurator instanceof UnknownServerConfigurator) {
configurable = false;
forceGeneration = false;
log.warn("Server will be considered as not configurable.");
}
if (!nuxeoConf.exists()) {
log.info("Missing " + nuxeoConf);
configurable = false;
userConfig = new CryptoProperties();
defaultConfig = new Properties();
} else if (userConfig == null || userConfig.size() == 0 || forceReload) {
try {
if (forceReload) {
// force 'templates' reload
templates = null;
}
setBasicConfiguration();
configurable = true;
} catch (ConfigurationException e) {
log.warn("Error reading basic configuration.", e);
configurable = false;
}
} else {
configurable = true;
}
return configurable;
}
/**
* @return Old templates
*/
public String changeTemplates(String newTemplates) {
String oldTemplates = templates;
templates = newTemplates;
try {
setBasicConfiguration(false);
configurable = true;
} catch (ConfigurationException e) {
log.warn("Error reading basic configuration.", e);
configurable = false;
}
return oldTemplates;
}
/**
* Change templates using given database template
*
* @param dbTemplate new database template
* @since 5.4.2
*/
public void changeDBTemplate(String dbTemplate) {
changeTemplates(rebuildTemplatesStr(dbTemplate));
}
private void setBasicConfiguration() throws ConfigurationException {
setBasicConfiguration(true);
}
private void setBasicConfiguration(boolean save) throws ConfigurationException {
try {
// Load default configuration
defaultConfig = loadTrimmedProperties(nuxeoDefaultConf);
// Add System properties
defaultConfig.putAll(System.getProperties());
userConfig = new CryptoProperties(defaultConfig);
// If Windows, replace backslashes in paths in nuxeo.conf
if (SystemUtils.IS_OS_WINDOWS) {
replaceBackslashes();
}
// Load user configuration
userConfig.putAll(loadTrimmedProperties(nuxeoConf));
onceGeneration = "once".equals(userConfig.getProperty(PARAM_FORCE_GENERATION));
forceGeneration = onceGeneration
|| Boolean.parseBoolean(userConfig.getProperty(PARAM_FORCE_GENERATION, "false"));
checkForDeprecatedParameters(userConfig);
// Synchronize directories between serverConfigurator and
// userConfig/defaultConfig
setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_DATA_DIR);
setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR);
setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_PID_DIR);
setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_TMP_DIR);
setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_MP_DIR);
} catch (NullPointerException e) {
throw new ConfigurationException("Missing file", e);
} catch (FileNotFoundException e) {
throw new ConfigurationException("Missing file: " + nuxeoDefaultConf + " or " + nuxeoConf, e);
} catch (IOException e) {
throw new ConfigurationException("Error reading " + nuxeoConf, e);
}
// Override default configuration with specific configuration(s) of
// the chosen template(s) which can be outside of server filesystem
try {
includeTemplates();
checkForDeprecatedParameters(defaultConfig);
extractDatabaseTemplateName();
extractNoSqlDatabaseTemplateName();
} catch (FileNotFoundException e) {
throw new ConfigurationException("Missing file", e);
} catch (IOException e) {
throw new ConfigurationException("Error reading " + nuxeoConf, e);
}
Map<String, String> newParametersToSave = evalDynamicProperties();
if (save && newParametersToSave != null && !newParametersToSave.isEmpty()) {
saveConfiguration(newParametersToSave, false, false);
}
logDebugInformation();
// Could be useful to initialize DEFAULT env...
// initEnv();
}
/**
* @since 5.7
* @throws IOException
*/
protected void includeTemplates() throws IOException {
includedTemplates.clear();
List<File> orderedTemplates = includeTemplates(getUserTemplates());
includedTemplates.clear();
includedTemplates.addAll(orderedTemplates);
log.debug(includedTemplates);
}
private void logDebugInformation() {
String debugPropValue = userConfig.getProperty(NUXEO_DEV_SYSTEM_PROP);
if (Boolean.parseBoolean(debugPropValue)) {
log.debug("Nuxeo Dev mode enabled");
} else {
log.debug("Nuxeo Dev mode is not enabled");
}
// XXX: cannot init seam debug mode when global debug mode is set, as
// it needs to be activated at startup, and requires the seam-debug jar
// to be in the classpath anyway
String seamDebugPropValue = userConfig.getProperty(SEAM_DEBUG_SYSTEM_PROP);
if (Boolean.parseBoolean(seamDebugPropValue)) {
log.debug("Nuxeo Seam HotReload is enabled");
} else {
log.debug("Nuxeo Seam HotReload is not enabled");
}
}
/**
* Generate properties which values are based on others
*
* @return Map with new parameters to save in {@code nuxeoConf}
* @throws ConfigurationException
* @since 5.5
*/
protected HashMap<String, String> evalDynamicProperties() throws ConfigurationException {
HashMap<String, String> newParametersToSave = new HashMap<>();
evalEnvironmentVariables(newParametersToSave);
evalLoopbackURL();
evalServerStatusKey(newParametersToSave);
return newParametersToSave;
}
/**
* Expand environment variable for properties values of the form ${env:MY_VAR}.
*
* @since 9.1
*/
protected void evalEnvironmentVariables(Map<String, String> newParametersToSave) {
for (Object keyObject : userConfig.keySet()) {
String key = (String) keyObject;
String value = userConfig.getProperty(key);
if (StringUtils.isNotBlank(value)) {
String newValue = replaceEnvironmentVariables(value);
if (!value.equals(newValue)) {
newParametersToSave.put(key, newValue);
}
}
}
}
private String replaceEnvironmentVariables(String value) {
if(StringUtils.isBlank(value)) {
return value;
}
Matcher matcher = ENV_VALUE_PATTERN.matcher(value);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
boolean booleanValue = "??".equals(matcher.group("boolean"));
String envVarName = matcher.group("envparam");
String defaultValue = matcher.group("defaultvalue");
String envValue = getEnvironmentVariableValue(envVarName);
String result;
if (booleanValue) {
result = StringUtils.isBlank(envValue) ? "false" : "true";
} else {
result = StringUtils.isBlank(envValue) ? defaultValue : envValue;
}
matcher.appendReplacement(sb, result);
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* Generate a server status key if not already set
*
* @throws ConfigurationException
* @see Environment#SERVER_STATUS_KEY
* @since 5.5
*/
private void evalServerStatusKey(Map<String, String> newParametersToSave) throws ConfigurationException {
if (userConfig.getProperty(Environment.SERVER_STATUS_KEY) == null) {
newParametersToSave.put(Environment.SERVER_STATUS_KEY, UUID.randomUUID().toString().substring(0, 8));
}
}
private void evalLoopbackURL() throws ConfigurationException {
String loopbackURL = userConfig.getProperty(PARAM_LOOPBACK_URL);
if (loopbackURL != null) {
log.debug("Using configured loop back url: " + loopbackURL);
return;
}
InetAddress bindAddress = getBindAddress();
// Address and ports already checked by #checkAddressesAndPorts
try {
if (bindAddress.isAnyLocalAddress()) {
boolean preferIPv6 = "false".equals(System.getProperty("java.net.preferIPv4Stack"))
&& "true".equals(System.getProperty("java.net.preferIPv6Addresses"));
bindAddress = preferIPv6 ? InetAddress.getByName("::1") : InetAddress.getByName("127.0.0.1");
log.debug("Bind address is \"ANY\", using local address instead: " + bindAddress);
}
} catch (UnknownHostException e) {
log.debug(e, e);
log.error(e.getMessage());
}
String httpPort = userConfig.getProperty(PARAM_HTTP_PORT);
String contextPath = userConfig.getProperty(PARAM_CONTEXT_PATH);
// Is IPv6 or IPv4 ?
if (bindAddress instanceof Inet6Address) {
loopbackURL = "http://[" + bindAddress.getHostAddress() + "]:" + httpPort + contextPath;
} else {
loopbackURL = "http://" + bindAddress.getHostAddress() + ":" + httpPort + contextPath;
}
log.debug("Set as loop back URL: " + loopbackURL);
defaultConfig.setProperty(PARAM_LOOPBACK_URL, loopbackURL);
}
/**
* Read nuxeo.conf, replace backslashes in paths and write new nuxeo.conf
*
* @throws ConfigurationException if any error reading or writing nuxeo.conf
* @since 5.4.1
*/
protected void replaceBackslashes() throws ConfigurationException {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(nuxeoConf))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.matches(".*:\\\\.*")) {
line = line.replaceAll("\\\\", "/");
}
sb.append(line).append(System.getProperty("line.separator"));
}
} catch (IOException e) {
throw new ConfigurationException("Error reading " + nuxeoConf, e);
}
try (FileWriter writer = new FileWriter(nuxeoConf, false)) {
// Copy back file content
writer.append(sb.toString());
} catch (IOException e) {
throw new ConfigurationException("Error writing in " + nuxeoConf, e);
}
}
/**
* @since 5.4.2
* @param key Directory system key
* @see Environment
*/
public void setDirectoryWithProperty(String key) {
String directory = userConfig.getProperty(key);
if (directory == null) {
defaultConfig.setProperty(key, serverConfigurator.getDirectory(key).getPath());
} else {
serverConfigurator.setDirectory(key, directory);
}
}
public String getUserTemplates() {
if (templates == null) {
templates = userConfig.getProperty(PARAM_TEMPLATES_NAME);
}
if (templates == null) {
log.warn("No template found in configuration! Fallback on 'default'.");
templates = "default";
}
userConfig.setProperty(PARAM_TEMPLATES_NAME, templates);
return templates;
}
protected void generateFiles() throws ConfigurationException {
try {
serverConfigurator.parseAndCopy(userConfig);
serverConfigurator.dumpProperties(userConfig);
log.info("Configuration files generated.");
// keep true or false, switch once to false
if (onceGeneration) {
setOnceToFalse = true;
writeConfiguration();
}
} catch (FileNotFoundException e) {
throw new ConfigurationException("Missing file: " + e.getMessage(), e);
} catch (TemplateException | ParseException e) {
throw new ConfigurationException("Could not process FreeMarker template: " + e.getMessage(), e);
} catch (IOException e) {
throw new ConfigurationException("Configuration failure: " + e.getMessage(), e);
}
}
private List<File> includeTemplates(String templatesList) throws IOException {
List<File> orderedTemplates = new ArrayList<>();
StringTokenizer st = new StringTokenizer(templatesList, TEMPLATE_SEPARATOR);
while (st.hasMoreTokens()) {
String nextToken = replaceEnvironmentVariables(st.nextToken());
File chosenTemplate = new File(nextToken);
// is it absolute and existing or relative path ?
if (!chosenTemplate.exists() || !chosenTemplate.getPath().equals(chosenTemplate.getAbsolutePath())) {
chosenTemplate = new File(nuxeoDefaultConf.getParentFile(), nextToken);
}
if (includedTemplates.contains(chosenTemplate)) {
log.debug("Already included " + nextToken);
continue;
}
if (!chosenTemplate.exists()) {
log.error(String.format(
"Template '%s' not found with relative or absolute path (%s). "
+ "Check your %s parameter, and %s for included files.",
nextToken, chosenTemplate, PARAM_TEMPLATES_NAME, PARAM_INCLUDED_TEMPLATES));
continue;
}
File chosenTemplateConf = new File(chosenTemplate, NUXEO_DEFAULT_CONF);
includedTemplates.add(chosenTemplate);
if (!chosenTemplateConf.exists()) {
log.warn("Ignore template (no default configuration): " + nextToken);
continue;
}
Properties subTemplateConf = loadTrimmedProperties(chosenTemplateConf);
String subTemplatesList = replaceEnvironmentVariables(
subTemplateConf.getProperty(PARAM_INCLUDED_TEMPLATES));
if (subTemplatesList != null && subTemplatesList.length() > 0) {
orderedTemplates.addAll(includeTemplates(subTemplatesList));
}
// Load configuration from chosen templates
defaultConfig.putAll(subTemplateConf);
orderedTemplates.add(chosenTemplate);
String templateInfo = "Include template: " + chosenTemplate.getPath();
if (quiet) {
log.debug(templateInfo);
} else {
log.info(templateInfo);
}
}
return orderedTemplates;
}
/**
* Check for deprecated parameters
*
* @since 5.6
*/
protected void checkForDeprecatedParameters(Properties properties) {
serverConfigurator.addServerSpecificParameters(parametersMigration);
@SuppressWarnings("rawtypes")
Enumeration userEnum = properties.propertyNames();
while (userEnum.hasMoreElements()) {
String key = (String) userEnum.nextElement();
if (parametersMigration.containsKey(key)) {
String value = properties.getProperty(key);
properties.setProperty(parametersMigration.get(key), value);
// Don't remove the deprecated key yet - more
// warnings but old things should keep working
// properties.remove(key);
if (!hideDeprecationWarnings) {
log.warn("Parameter " + key + " is deprecated - please use " + parametersMigration.get(key)
+ " instead");
}
}
}
}
public File getNuxeoHome() {
return nuxeoHome;
}
public File getNuxeoDefaultConf() {
return nuxeoDefaultConf;
}
public List<File> getIncludedTemplates() {
return includedTemplates;
}
/**
* Save changed parameters in {@code nuxeo.conf}. This method does not check values in map. Use
* {@link #saveFilteredConfiguration(Map)} for parameters filtering.
*
* @param changedParameters Map of modified parameters
* @see #saveFilteredConfiguration(Map)
*/
public void saveConfiguration(Map<String, String> changedParameters) throws ConfigurationException {
// Keep generation true or once; switch false to once
saveConfiguration(changedParameters, false, true);
}
/**
* Save changed parameters in {@code nuxeo.conf} calculating templates if changedParameters contains a value for
* {@link #PARAM_TEMPLATE_DBNAME}. If a parameter value is empty ("" or null), then the property is unset.
* {@link #PARAM_WIZARD_DONE}, {@link #PARAM_TEMPLATES_NAME} and {@link #PARAM_FORCE_GENERATION} cannot be unset,
* but their value can be changed.<br/>
* This method does not check values in map: use {@link #saveFilteredConfiguration(Map)} for parameters filtering.
*
* @param changedParameters Map of modified parameters
* @param setGenerationOnceToFalse If generation was on (true or once), then set it to false or not?
* @param setGenerationFalseToOnce If generation was off (false), then set it to once?
* @see #saveFilteredConfiguration(Map)
* @since 5.5
*/
public void saveConfiguration(Map<String, String> changedParameters, boolean setGenerationOnceToFalse,
boolean setGenerationFalseToOnce) throws ConfigurationException {
setOnceToFalse = setGenerationOnceToFalse;
setFalseToOnce = setGenerationFalseToOnce;
updateStoredConfig();
String newDbTemplate = changedParameters.remove(PARAM_TEMPLATE_DBNAME);
if (newDbTemplate != null) {
changedParameters.put(PARAM_TEMPLATES_NAME, rebuildTemplatesStr(newDbTemplate));
}
newDbTemplate = changedParameters.remove(PARAM_TEMPLATE_DBNOSQL_NAME);
if (newDbTemplate != null) {
changedParameters.put(PARAM_TEMPLATES_NAME, rebuildTemplatesStr(newDbTemplate));
}
if (changedParameters.containsValue(null) || changedParameters.containsValue("")) {
// There are properties to unset
Set<String> propertiesToUnset = new HashSet<>();
for (Entry<String, String> entry : changedParameters.entrySet()) {
if (StringUtils.isEmpty(entry.getValue())) {
propertiesToUnset.add(entry.getKey());
}
}
for (String key : propertiesToUnset) {
changedParameters.remove(key);
userConfig.remove(key);
}
}
userConfig.putAll(changedParameters);
writeConfiguration();
updateStoredConfig();
}
private void updateStoredConfig() {
if (storedConfig == null) {
storedConfig = new Properties(defaultConfig);
} else {
storedConfig.clear();
}
storedConfig.putAll(userConfig);
}
/**
* Save changed parameters in {@code nuxeo.conf}, filtering parameters with {@link #getChangedParameters(Map)}
*
* @param changedParameters Maps of modified parameters
* @since 5.4.2
* @see #saveConfiguration(Map)
* @see #getChangedParameters(Map)
*/
public void saveFilteredConfiguration(Map<String, String> changedParameters) throws ConfigurationException {
Map<String, String> filteredParameters = getChangedParameters(changedParameters);
saveConfiguration(filteredParameters);
}
/**
* Filters given parameters including them only if (there was no previous value and new value is not empty/null) or
* (there was a previous value and it differs from the new value)
*
* @param changedParameters parameters to be filtered
* @return filtered map
* @since 5.4.2
*/
public Map<String, String> getChangedParameters(Map<String, String> changedParameters) {
Map<String, String> filteredChangedParameters = new HashMap<>();
for (String key : changedParameters.keySet()) {
String oldParam = getStoredConfig().getProperty(key);
String newParam = changedParameters.get(key);
if (newParam != null) {
newParam = newParam.trim();
}
if (oldParam == null && StringUtils.isNotEmpty(newParam)
|| oldParam != null && !oldParam.trim().equals(newParam)) {
filteredChangedParameters.put(key, newParam);
}
}
return filteredChangedParameters;
}
private void writeConfiguration() throws ConfigurationException {
final MessageDigest newContentDigest = DigestUtils.getMd5Digest();
StringWriter newContent = new StringWriter() {
@Override
public void write(String str) {
if (str != null) {
newContentDigest.update(str.getBytes());
}
super.write(str);
}
};
// Copy back file content
newContent.append(readConfiguration());
// Write changed parameters
newContent.write(BOUNDARY_BEGIN + System.getProperty("line.separator"));
for (Object o : new TreeSet<>(userConfig.keySet())) {
String key = (String) o;
// Ignore parameters already stored in newContent
if (PARAM_FORCE_GENERATION.equals(key) || PARAM_WIZARD_DONE.equals(key)
|| PARAM_TEMPLATES_NAME.equals(key)) {
continue;
}
String oldValue = storedConfig.getProperty(key, "");
String newValue = userConfig.getRawProperty(key, "");
if (!newValue.equals(oldValue)) {
newContent.write("#" + key + "=" + oldValue + System.getProperty("line.separator"));
newContent.write(key + "=" + newValue + System.getProperty("line.separator"));
}
}
newContent.write(BOUNDARY_END + System.getProperty("line.separator"));
// Write file only if content has changed
if (!Hex.encodeHexString(newContentDigest.digest()).equals(currentConfigurationDigest)) {
try (Writer writer = new FileWriter(nuxeoConf, false)) {
writer.append(newContent.getBuffer());
} catch (IOException e) {
throw new ConfigurationException("Error writing in " + nuxeoConf, e);
}
}
}
private StringBuffer readConfiguration() throws ConfigurationException {
// Will change wizardParam value instead of appending it
String wizardParam = userConfig.getProperty(PARAM_WIZARD_DONE);
// Will change templatesParam value instead of appending it
String templatesParam = userConfig.getProperty(PARAM_TEMPLATES_NAME);
Integer generationIndex = null, wizardIndex = null, templatesIndex = null;
List<String> newLines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(nuxeoConf))) {
String line;
MessageDigest digest = DigestUtils.getMd5Digest();
boolean onConfiguratorContent = false;
while ((line = reader.readLine()) != null) {
digest.update(line.getBytes());
if (!onConfiguratorContent) {
if (!line.startsWith(BOUNDARY_BEGIN)) {
if (line.startsWith(PARAM_FORCE_GENERATION)) {
if (setOnceToFalse && onceGeneration) {
line = PARAM_FORCE_GENERATION + "=false";
}
if (setFalseToOnce && !forceGeneration) {
line = PARAM_FORCE_GENERATION + "=once";
}
if (generationIndex == null) {
newLines.add(line);
generationIndex = newLines.size() - 1;
} else {
newLines.set(generationIndex, line);
}
} else if (line.startsWith(PARAM_WIZARD_DONE)) {
if (wizardParam != null) {
line = PARAM_WIZARD_DONE + "=" + wizardParam;
}
if (wizardIndex == null) {
newLines.add(line);
wizardIndex = newLines.size() - 1;
} else {
newLines.set(wizardIndex, line);
}
} else if (line.startsWith(PARAM_TEMPLATES_NAME)) {
if (templatesParam != null) {
line = PARAM_TEMPLATES_NAME + "=" + templatesParam;
}
if (templatesIndex == null) {
newLines.add(line);
templatesIndex = newLines.size() - 1;
} else {
newLines.set(templatesIndex, line);
}
} else {
int equalIdx = line.indexOf("=");
if (equalIdx < 1 || line.trim().startsWith("#")) {
newLines.add(line);
} else {
String key = line.substring(0, equalIdx).trim();
if (userConfig.getProperty(key) != null) {
newLines.add(line);
} else {
newLines.add("#" + line);
}
}
}
} else {
// What must be written just before the BOUNDARY_BEGIN
if (templatesIndex == null && templatesParam != null) {
newLines.add(PARAM_TEMPLATES_NAME + "=" + templatesParam);
templatesIndex = newLines.size() - 1;
}
if (wizardIndex == null && wizardParam != null) {
newLines.add(PARAM_WIZARD_DONE + "=" + wizardParam);
wizardIndex = newLines.size() - 1;
}
onConfiguratorContent = true;
}
} else {
if (!line.startsWith(BOUNDARY_END)) {
int equalIdx = line.indexOf("=");
if (line.startsWith("#" + PARAM_TEMPLATES_NAME) || line.startsWith(PARAM_TEMPLATES_NAME)) {
// Backward compliance, it must be ignored
continue;
}
if (equalIdx < 1) { // Ignore non-readable lines
continue;
}
if (line.trim().startsWith("#")) {
String key = line.substring(1, equalIdx).trim();
String value = line.substring(equalIdx + 1).trim();
getStoredConfig().setProperty(key, value);
} else {
String key = line.substring(0, equalIdx).trim();
String value = line.substring(equalIdx + 1).trim();
if (!value.equals(userConfig.getRawProperty(key))) {
getStoredConfig().setProperty(key, value);
}
}
} else {
onConfiguratorContent = false;
}
}
}
reader.close();
currentConfigurationDigest = Hex.encodeHexString(digest.digest());
} catch (IOException e) {
throw new ConfigurationException("Error reading " + nuxeoConf, e);
}
StringBuffer newContent = new StringBuffer();
for (String newLine : newLines) {
newContent.append(newLine.trim()).append(System.getProperty("line.separator"));
}
return newContent;
}
/**
* Extract a database template from the current list of templates. Return the last one if there are multiples.
*
* @see #rebuildTemplatesStr(String)
*/
public String extractDatabaseTemplateName() {
return extractDbTemplateName(DB_LIST, PARAM_TEMPLATE_DBTYPE, PARAM_TEMPLATE_DBNAME, "unknown");
}
/**
* Extract a NoSQL database template from the current list of templates. Return the last one if there are multiples.
*
* @see #rebuildTemplatesStr(String)
* @since 8.1
*/
public String extractNoSqlDatabaseTemplateName() {
return extractDbTemplateName(DB_NOSQL_LIST, PARAM_TEMPLATE_DBNOSQL_TYPE, PARAM_TEMPLATE_DBNOSQL_NAME, null);
}
private String extractDbTemplateName(List<String> knownDbList, String paramTemplateDbType,
String paramTemplateDbName, String defaultTemplate) {
String dbTemplate = defaultTemplate;
boolean found = false;
for (File templateFile : includedTemplates) {
String template = templateFile.getName();
if (knownDbList.contains(template)) {
dbTemplate = template;
found = true;
}
}
String dbType = userConfig.getProperty(paramTemplateDbType);
if (!found && dbType != null) {
log.warn(String.format("Didn't find a known database template in the list but "
+ "some template contributed a value for %s.", paramTemplateDbType));
dbTemplate = dbType;
}
if (dbTemplate != null && !dbTemplate.equals(dbType)) {
if (dbType == null) {
log.warn(String.format("Missing value for %s, using %s", paramTemplateDbType, dbTemplate));
userConfig.setProperty(paramTemplateDbType, dbTemplate);
} else {
log.debug(String.format("Different values between %s (%s) and %s (%s)", paramTemplateDbName, dbTemplate,
paramTemplateDbType, dbType));
}
}
if (dbTemplate == null) {
defaultConfig.remove(paramTemplateDbName);
} else {
defaultConfig.setProperty(paramTemplateDbName, dbTemplate);
}
return dbTemplate;
}
/**
* @return nuxeo.conf file used
*/
public File getNuxeoConf() {
return nuxeoConf;
}
/**
* Delegate logs initialization to serverConfigurator instance
*
* @since 5.4.2
*/
public void initLogs() {
serverConfigurator.initLogs();
}
/**
* @return log directory
* @since 5.4.2
*/
public File getLogDir() {
return serverConfigurator.getLogDir();
}
/**
* @return pid directory
* @since 5.4.2
*/
public File getPidDir() {
return serverConfigurator.getPidDir();
}
/**
* @return Data directory
* @since 5.4.2
*/
public File getDataDir() {
return serverConfigurator.getDataDir();
}
/**
* Create needed directories. Check existence of old paths. If old paths have been found and they cannot be upgraded
* automatically, then upgrading message is logged and error thrown.
*
* @throws ConfigurationException If a deprecated directory has been detected.
* @since 5.4.2
* @see ServerConfigurator#verifyInstallation()
*/
public void verifyInstallation() throws ConfigurationException {
checkJavaVersion();
ifNotExistsAndIsDirectoryThenCreate(getLogDir());
ifNotExistsAndIsDirectoryThenCreate(getPidDir());
ifNotExistsAndIsDirectoryThenCreate(getDataDir());
ifNotExistsAndIsDirectoryThenCreate(getTmpDir());
ifNotExistsAndIsDirectoryThenCreate(getPackagesDir());
checkAddressesAndPorts();
serverConfigurator.verifyInstallation();
backingServicesConfigurator.verifyInstallation();
}
/**
* @return Marketplace packages directory
* @since 5.9.4
*/
private File getPackagesDir() {
return serverConfigurator.getPackagesDir();
}
/**
* Check that the process is executed with a supported Java version. See
* <a href="http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html">J2SE SDK/JRE Version String
* Naming Convention</a>
*
* @throws ConfigurationException
* @since 5.6
*/
public void checkJavaVersion() throws ConfigurationException {
String version = System.getProperty("java.version");
checkJavaVersion(version, COMPLIANT_JAVA_VERSIONS);
}
/**
* Check the java version compared to compliant ones.
*
* @param version the java version
* @param compliantVersions the compliant java versions
* @since 9.1
*/
protected static void checkJavaVersion(String version, String[] compliantVersions) throws ConfigurationException {
// compliantVersions represents the java versions on which Nuxeo runs perfectly, so:
// - if we run Nuxeo with a major java version present in compliantVersions and compatible with then this
// method exits without error and without logging a warn message about loose compliance
// - if we run Nuxeo with a major java version not present in compliantVersions but greater than once then
// this method exits without error and logs a warn message about loose compliance
// - if we run Nuxeo with a non valid java version then method exits with error
// - if we run Nuxeo with a non valid java version and with jvmcheck=nofail property then method exits without
// error and logs a warn message about loose compliance
// try to retrieve the closest compliant java version
String lastCompliantVersion = null;
for (String compliantVersion : compliantVersions) {
if (checkJavaVersion(version, compliantVersion, false, false)) {
// current compliant version is valid, go to next one
lastCompliantVersion = compliantVersion;
} else if (lastCompliantVersion != null) {
// current compliant version is not valid, but we found a valid one earlier, 1st case
return;
} else if (checkJavaVersion(version, compliantVersion, true, true)) {
// current compliant version is not valid, try to check java version with jvmcheck=nofail, 4th case
// here we will log about loose compliance for the lower compliant java version
return;
}
}
// we might have lastCompliantVersion, unless nothing is valid against the current java version
if (lastCompliantVersion != null) {
// 2nd case: log about loose compliance if current major java version is greater than the greatest
// compliant java version
checkJavaVersion(version, lastCompliantVersion, false, true);
return;
}
// 3th case
String message = String.format("Nuxeo requires Java %s (detected %s).", ArrayUtils.toString(compliantVersions),
version);
throw new ConfigurationException(message + " See '" + JVMCHECK_PROP + "' option to bypass version check.");
}
/**
* Checks the java version compared to the required one.
* <p>
* Loose compliance is assumed if the major version is greater than the required major version or a jvmcheck=nofail
* flag is set.
*
* @param version the java version
* @param requiredVersion the required java version
* @param allowNoFailFlag if {@code true} then check jvmcheck=nofail flag to always have loose compliance
* @param warnIfLooseCompliance if {@code true} then log a WARN if the is loose compliance
* @return true if the java version is compliant (maybe loosely) with the required version
* @since 8.4
*/
protected static boolean checkJavaVersion(String version, String requiredVersion, boolean allowNoFailFlag,
boolean warnIfLooseCompliance) {
allowNoFailFlag = allowNoFailFlag
&& JVMCHECK_NOFAIL.equalsIgnoreCase(System.getProperty(JVMCHECK_PROP, JVMCHECK_FAIL));
try {
JVMVersion required = JVMVersion.parse(requiredVersion);
JVMVersion actual = JVMVersion.parse(version);
boolean compliant = actual.compareTo(required) >= 0;
if (compliant && actual.compareTo(required, UpTo.MAJOR) == 0) {
return true;
}
if (!compliant && !allowNoFailFlag) {
return false;
}
// greater major version or noFail is present in system property, considered loosely compliant but may warn
if (warnIfLooseCompliance) {
log.warn(String.format("Nuxeo requires Java %s+ (detected %s).", requiredVersion, version));
}
return true;
} catch (java.text.ParseException cause) {
if (allowNoFailFlag) {
log.warn("Cannot check java version", cause);
return true;
}
throw new IllegalArgumentException("Cannot check java version", cause);
}
}
/**
* Checks the java version compared to the required one.
* <p>
* If major version is same as required major version and minor is greater or equal, it is compliant.
* <p>
* If major version is greater than required major version, it is compliant.
*
* @param version the java version
* @param requiredVersion the required java version
* @return true if the java version is compliant with the required version
* @since 8.4
*/
public static boolean checkJavaVersion(String version, String requiredVersion) {
return checkJavaVersion(version, requiredVersion, false, false);
}
/**
* Will check the configured addresses are reachable and Nuxeo required ports are available on those addresses.
* Server specific implementations should override this method in order to check for server specific ports.
* {@link #PARAM_BIND_ADDRESS} must be set before.
*
* @throws ConfigurationException
* @since 5.5
* @see ServerConfigurator#verifyInstallation()
*/
public void checkAddressesAndPorts() throws ConfigurationException {
InetAddress bindAddress = getBindAddress();
// Sanity check
if (bindAddress.isMulticastAddress()) {
throw new ConfigurationException("Multicast address won't work: " + bindAddress);
}
checkAddressReachable(bindAddress);
checkPortAvailable(bindAddress, Integer.parseInt(userConfig.getProperty(PARAM_HTTP_PORT)));
}
public InetAddress getBindAddress() throws ConfigurationException {
InetAddress bindAddress;
try {
bindAddress = InetAddress.getByName(userConfig.getProperty(PARAM_BIND_ADDRESS));
if (bindAddress.isAnyLocalAddress()) {
boolean preferIPv6 = "false".equals(System.getProperty("java.net.preferIPv4Stack"))
&& "true".equals(System.getProperty("java.net.preferIPv6Addresses"));
bindAddress = preferIPv6 ? InetAddress.getByName("::1") : InetAddress.getByName("127.0.0.1");
log.debug("Bind address is \"ANY\", using local address instead: " + bindAddress);
}
log.debug("Configured bind address: " + bindAddress);
} catch (UnknownHostException e) {
throw new ConfigurationException(e);
}
return bindAddress;
}
/**
* @param address address to check for availability
* @throws ConfigurationException
* @since 5.5
*/
public static void checkAddressReachable(InetAddress address) throws ConfigurationException {
try {
log.debug("Checking availability of " + address);
address.isReachable(ADDRESS_PING_TIMEOUT);
} catch (IOException e) {
throw new ConfigurationException("Unreachable bind address " + address);
}
}
/**
* Checks if port is available on given address.
*
* @param port port to check for availability
* @throws ConfigurationException Throws an exception if address is unavailable.
* @since 5.5
*/
public static void checkPortAvailable(InetAddress address, int port) throws ConfigurationException {
if ((port == 0) || (port == -1)) {
log.warn("Port is set to " + Integer.toString(port)
+ " - assuming it is disabled - skipping availability check");
return;
}
if (port < MIN_PORT || port > MAX_PORT) {
throw new IllegalArgumentException("Invalid port: " + port);
}
ServerSocket socketTCP = null;
// DatagramSocket socketUDP = null;
try {
log.debug("Checking availability of port " + port + " on address " + address);
socketTCP = new ServerSocket(port, 0, address);
socketTCP.setReuseAddress(true);
// socketUDP = new DatagramSocket(port, address);
// socketUDP.setReuseAddress(true);
// return true;
} catch (IOException e) {
throw new ConfigurationException(e.getMessage() + ": " + address + ":" + port, e);
} finally {
// if (socketUDP != null) {
// socketUDP.close();
// }
if (socketTCP != null) {
try {
socketTCP.close();
} catch (IOException e) {
// Do not throw
}
}
}
}
/**
* @return Temporary directory
*/
public File getTmpDir() {
return serverConfigurator.getTmpDir();
}
private void ifNotExistsAndIsDirectoryThenCreate(File directory) {
if (!directory.isDirectory()) {
directory.mkdirs();
}
}
/**
* @return Log files produced by Log4J configuration without loading this configuration instead of current active
* one.
* @since 5.4.2
*/
public ArrayList<String> getLogFiles() {
File log4jConfFile = serverConfigurator.getLogConfFile();
System.setProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR, getLogDir().getPath());
return Log4JHelper.getFileAppendersFiles(log4jConfFile);
}
/**
* Check if wizard must and can be ran
*
* @return true if configuration wizard is required before starting Nuxeo
* @since 5.4.2
*/
public boolean isWizardRequired() {
return !"true".equalsIgnoreCase(getUserConfig().getProperty(PARAM_WIZARD_DONE, "true"))
&& serverConfigurator.isWizardAvailable();
}
/**
* Rebuild a templates string for use in nuxeo.conf
*
* @param dbTemplate database template to use instead of current one
* @return new templates string using given dbTemplate
* @since 5.4.2
* @see #extractDatabaseTemplateName()
* @see #changeDBTemplate(String)
* @see #changeTemplates(String)
*/
public String rebuildTemplatesStr(String dbTemplate) {
List<String> templatesList = new ArrayList<>();
templatesList.addAll(Arrays.asList(templates.split(TEMPLATE_SEPARATOR)));
String currentDBTemplate = null;
if (DB_LIST.contains(dbTemplate)) {
currentDBTemplate = userConfig.getProperty(PARAM_TEMPLATE_DBNAME);
if (currentDBTemplate == null) {
currentDBTemplate = extractDatabaseTemplateName();
}
} else if (DB_NOSQL_LIST.contains(dbTemplate)) {
currentDBTemplate = userConfig.getProperty(PARAM_TEMPLATE_DBNOSQL_NAME);
if (currentDBTemplate == null) {
currentDBTemplate = extractNoSqlDatabaseTemplateName();
}
if ("none".equals(dbTemplate)) {
dbTemplate = null;
}
}
int dbIdx = templatesList.indexOf(currentDBTemplate);
if (dbIdx < 0) {
if (dbTemplate == null) {
return templates;
}
// current db template is implicit => set the new one
templatesList.add(dbTemplate);
} else if (dbTemplate == null) {
// current db template is explicit => remove it
templatesList.remove(dbIdx);
} else {
// current db template is explicit => replace it
templatesList.set(dbIdx, dbTemplate);
}
return replaceEnvironmentVariables(StringUtils.join(templatesList, TEMPLATE_SEPARATOR));
}
/**
* @return Nuxeo config directory
* @since 5.4.2
*/
public File getConfigDir() {
return serverConfigurator.getConfigDir();
}
/**
* Ensure the server will start only wizard application, not Nuxeo
*
* @since 5.4.2
*/
public void prepareWizardStart() {
serverConfigurator.prepareWizardStart();
}
/**
* Ensure the wizard won't be started and nuxeo is ready for use
*
* @since 5.4.2
*/
public void cleanupPostWizard() {
serverConfigurator.cleanupPostWizard();
}
/**
* @return Nuxeo runtime home
*/
public File getRuntimeHome() {
return serverConfigurator.getRuntimeHome();
}
/**
* @since 5.4.2
* @return true if there's an install in progress
*/
public boolean isInstallInProgress() {
return getInstallFile().exists();
}
/**
* @return File pointing to the directory containing the marketplace packages included in the distribution
* @since 5.6
*/
public File getDistributionMPDir() {
String mpDir = userConfig.getProperty(PARAM_MP_DIR, DISTRIBUTION_MP_DIR);
return new File(getNuxeoHome(), mpDir);
}
/**
* @return Install/upgrade file
* @since 5.4.1
*/
public File getInstallFile() {
return new File(serverConfigurator.getDataDir(), INSTALL_AFTER_RESTART);
}
/**
* Add template(s) to the {@link #PARAM_TEMPLATES_NAME} list if not already present
*
* @param templatesToAdd Comma separated templates to add
* @throws ConfigurationException
* @since 5.5
*/
public void addTemplate(String templatesToAdd) throws ConfigurationException {
List<String> templatesList = getTemplateList();
List<String> templatesToAddList = Arrays.asList(templatesToAdd.split(TEMPLATE_SEPARATOR));
if (templatesList.addAll(templatesToAddList)) {
String newTemplatesStr = StringUtils.join(templatesList, TEMPLATE_SEPARATOR);
HashMap<String, String> parametersToSave = new HashMap<>();
parametersToSave.put(PARAM_TEMPLATES_NAME, newTemplatesStr);
saveFilteredConfiguration(parametersToSave);
changeTemplates(newTemplatesStr);
}
}
/**
* Return the list of templates.
* @return
* @since 9.2
*/
public List<String> getTemplateList() {
String currentTemplatesStr = userConfig.getProperty(PARAM_TEMPLATES_NAME);
List<String> templatesList = new ArrayList<>();
templatesList.addAll(Arrays.asList(currentTemplatesStr.split(TEMPLATE_SEPARATOR)));
return templatesList;
}
/**
* Remove template(s) from the {@link #PARAM_TEMPLATES_NAME} list
*
* @param templatesToRm Comma separated templates to remove
* @throws ConfigurationException
* @since 5.5
*/
public void rmTemplate(String templatesToRm) throws ConfigurationException {
List<String> templatesList = getTemplateList();
List<String> templatesToRmList = Arrays.asList(templatesToRm.split(TEMPLATE_SEPARATOR));
if (templatesList.removeAll(templatesToRmList)) {
String newTemplatesStr = StringUtils.join(templatesList, TEMPLATE_SEPARATOR);
HashMap<String, String> parametersToSave = new HashMap<>();
parametersToSave.put(PARAM_TEMPLATES_NAME, newTemplatesStr);
saveFilteredConfiguration(parametersToSave);
changeTemplates(newTemplatesStr);
}
}
/**
* Set a property in nuxeo configuration
*
* @throws ConfigurationException
* @return The old value
* @since 5.5
*/
public String setProperty(String key, String value) throws ConfigurationException {
String oldValue = getStoredConfig().getProperty(key);
if (PARAM_TEMPLATES_NAME.equals(key)) {
templates = StringUtils.isBlank(value) ? null : value;
}
HashMap<String, String> newParametersToSave = new HashMap<>();
newParametersToSave.put(key, value);
saveFilteredConfiguration(newParametersToSave);
setBasicConfiguration();
return oldValue;
}
/**
* Set properties in nuxeo configuration
*
* @return The old values
* @throws ConfigurationException
* @since 7.4
*/
public Map<String, String> setProperties(Map<String, String> newParametersToSave) throws ConfigurationException {
Map<String, String> oldValues = new HashMap<>();
for (String key : newParametersToSave.keySet()) {
oldValues.put(key, getStoredConfig().getProperty(key));
if (PARAM_TEMPLATES_NAME.equals(key)) {
String value = newParametersToSave.get(key);
templates = StringUtils.isBlank(value) ? null : value;
}
}
saveFilteredConfiguration(newParametersToSave);
setBasicConfiguration();
return oldValues;
}
/**
* Set properties in the given template, if it exists
*
* @return The old values
* @throws ConfigurationException
* @throws IOException
* @since 7.4
*/
public Map<String, String> setProperties(String template, Map<String, String> newParametersToSave)
throws ConfigurationException, IOException {
File templateConf = getTemplateConf(template);
Properties templateProperties = loadTrimmedProperties(templateConf);
Map<String, String> oldValues = new HashMap<>();
StringBuilder newContent = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(templateConf))) {
String line = reader.readLine();
if (line != null && line.startsWith("## DO NOT EDIT THIS FILE")) {
throw new ConfigurationException("The template states in its header that it must not be modified.");
}
while (line != null) {
int equalIdx = line.indexOf("=");
if (equalIdx < 1 || line.trim().startsWith("#")) {
newContent.append(line).append(System.getProperty("line.separator"));
} else {
String key = line.substring(0, equalIdx).trim();
if (newParametersToSave.containsKey(key)) {
newContent.append(key).append("=").append(newParametersToSave.get(key)).append(
System.getProperty("line.separator"));
} else {
newContent.append(line).append(System.getProperty("line.separator"));
}
}
line = reader.readLine();
}
}
for (String key : newParametersToSave.keySet()) {
if (templateProperties.containsKey(key)) {
oldValues.put(key, templateProperties.getProperty(key));
} else {
newContent.append(key).append("=").append(newParametersToSave.get(key)).append(
System.getProperty("line.separator"));
}
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(templateConf))) {
writer.append(newContent.toString());
}
setBasicConfiguration();
return oldValues;
}
/**
* Check driver availability and database connection
*
* @param databaseTemplate Nuxeo database template
* @param dbName nuxeo.db.name parameter in nuxeo.conf
* @param dbUser nuxeo.db.user parameter in nuxeo.conf
* @param dbPassword nuxeo.db.password parameter in nuxeo.conf
* @param dbHost nuxeo.db.host parameter in nuxeo.conf
* @param dbPort nuxeo.db.port parameter in nuxeo.conf
* @throws DatabaseDriverException
* @throws IOException
* @throws FileNotFoundException
* @throws SQLException
* @since 5.6
*/
public void checkDatabaseConnection(String databaseTemplate, String dbName, String dbUser, String dbPassword,
String dbHost, String dbPort)
throws FileNotFoundException, IOException, DatabaseDriverException, SQLException {
File databaseTemplateDir = new File(nuxeoHome, TEMPLATES + File.separator + databaseTemplate);
Properties templateProperties = loadTrimmedProperties(new File(databaseTemplateDir, NUXEO_DEFAULT_CONF));
String classname, connectionUrl;
if (userConfig.getProperty(PARAM_TEMPLATE_DBNAME).equals(databaseTemplateDir)) {
// userConfig already includes databaseTemplate
classname = userConfig.getProperty(PARAM_DB_DRIVER);
connectionUrl = userConfig.getProperty(PARAM_DB_JDBC_URL);
} else { // testing a databaseTemplate not included in userConfig
// check if value is set in nuxeo.conf
if (userConfig.containsKey(PARAM_DB_DRIVER)) {
classname = (String) userConfig.get(PARAM_DB_DRIVER);
} else {
classname = templateProperties.getProperty(PARAM_DB_DRIVER);
}
if (userConfig.containsKey(PARAM_DB_JDBC_URL)) {
connectionUrl = (String) userConfig.get(PARAM_DB_JDBC_URL);
} else {
connectionUrl = templateProperties.getProperty(PARAM_DB_JDBC_URL);
}
}
// Load driver class from template or default lib directory
Driver driver = lookupDriver(databaseTemplate, databaseTemplateDir, classname);
// Test db connection
DriverManager.registerDriver(driver);
Properties ttProps = new Properties(userConfig);
ttProps.put(PARAM_DB_HOST, dbHost);
ttProps.put(PARAM_DB_PORT, dbPort);
ttProps.put(PARAM_DB_NAME, dbName);
ttProps.put(PARAM_DB_USER, dbUser);
ttProps.put(PARAM_DB_PWD, dbPassword);
TextTemplate tt = new TextTemplate(ttProps);
String url = tt.processText(connectionUrl);
Properties conProps = new Properties();
conProps.put("user", dbUser);
conProps.put("password", dbPassword);
log.debug("Testing URL " + url + " with " + conProps);
Connection con = driver.connect(url, conProps);
con.close();
}
/**
* Build an {@link URLClassLoader} for the given databaseTemplate looking in the templates directory and in the
* server lib directory, then looks for a driver
*
* @param classname Driver class name, defined by {@link #PARAM_DB_DRIVER}
* @return Driver driver if found, else an Exception must have been raised.
* @throws IOException
* @throws FileNotFoundException
* @throws DatabaseDriverException If there was an error when trying to instantiate the driver.
* @since 5.6
*/
private Driver lookupDriver(String databaseTemplate, File databaseTemplateDir, String classname)
throws FileNotFoundException, IOException, DatabaseDriverException {
File[] files = (File[]) ArrayUtils.addAll( //
new File(databaseTemplateDir, "lib").listFiles(), //
serverConfigurator.getServerLibDir().listFiles());
List<URL> urlsList = new ArrayList<>();
if (files != null) {
for (File file : files) {
if (file.getName().endsWith("jar")) {
try {
urlsList.add(new URL("jar:file:" + file.getPath() + "!/"));
log.debug("Added " + file.getPath());
} catch (MalformedURLException e) {
log.error(e);
}
}
}
}
URLClassLoader ucl = new URLClassLoader(urlsList.toArray(new URL[0]));
try {
return (Driver) Class.forName(classname, true, ucl).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new DatabaseDriverException(e);
}
}
/**
* @since 5.6
* @return an {@link Environment} initialized with a few basics
*/
public Environment getEnv() {
/*
* It could be useful to initialize DEFAULT env in {@link #setBasicConfiguration()}... For now, the generated
* {@link Environment} is not static.
*/
if (env == null) {
env = new Environment(getRuntimeHome());
File distribFile = new File(new File(nuxeoHome, TEMPLATES), "common/config/distribution.properties");
if (distribFile.exists()) {
try {
env.loadProperties(loadTrimmedProperties(distribFile));
} catch (IOException e) {
log.error(e);
}
}
env.loadProperties(userConfig);
env.setServerHome(getNuxeoHome());
env.init();
env.setData(userConfig.getProperty(Environment.NUXEO_DATA_DIR, "data"));
env.setLog(userConfig.getProperty(Environment.NUXEO_LOG_DIR, "logs"));
env.setTemp(userConfig.getProperty(Environment.NUXEO_TMP_DIR, "tmp"));
env.setPath(PARAM_MP_DIR, getDistributionMPDir(), env.getServerHome());
env.setPath(Environment.NUXEO_MP_DIR, getPackagesDir(), env.getServerHome());
}
return env;
}
/**
* @since 5.6
* @param propsFile Properties file
* @return new Properties containing trimmed keys and values read in {@code propsFile}
* @throws IOException
*/
public static Properties loadTrimmedProperties(File propsFile) throws IOException {
Properties props = new Properties();
try (FileInputStream propsIS = new FileInputStream(propsFile)) {
loadTrimmedProperties(props, propsIS);
}
return props;
}
/**
* @since 5.6
* @param props Properties object to be filled
* @param propsIS Properties InputStream
* @throws IOException
*/
public static void loadTrimmedProperties(Properties props, InputStream propsIS) throws IOException {
if (props == null) {
return;
}
Properties p = new Properties();
p.load(propsIS);
@SuppressWarnings("unchecked")
Enumeration<String> pEnum = (Enumeration<String>) p.propertyNames();
while (pEnum.hasMoreElements()) {
String key = pEnum.nextElement();
String value = p.getProperty(key);
props.put(key.trim(), value.trim());
}
}
/**
* @return The generated properties file with dumped configuration.
* @since 5.6
*/
public File getDumpedConfig() {
return new File(getConfigDir(), CONFIGURATION_PROPERTIES);
}
/**
* Build a {@link Hashtable} which contains environment properties to instantiate a {@link InitialDirContext}
*
* @since 6.0
*/
public Hashtable<Object, Object> getContextEnv(String ldapUrl, String bindDn, String bindPassword,
boolean checkAuthentication) {
Hashtable<Object, Object> contextEnv = new Hashtable<>();
contextEnv.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
contextEnv.put("com.sun.jndi.ldap.connect.timeout", "10000");
contextEnv.put(javax.naming.Context.PROVIDER_URL, ldapUrl);
if (checkAuthentication) {
contextEnv.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple");
contextEnv.put(javax.naming.Context.SECURITY_PRINCIPAL, bindDn);
contextEnv.put(javax.naming.Context.SECURITY_CREDENTIALS, bindPassword);
}
return contextEnv;
}
/**
* Check if the LDAP parameters are correct to bind to a LDAP server. if authenticate argument is true, it will also
* check if the authentication against the LDAP server succeeds
*
* @param authenticate Indicates if authentication against LDAP should be checked.
* @since 6.0
*/
public void checkLdapConnection(String ldapUrl, String ldapBindDn, String ldapBindPwd, boolean authenticate)
throws NamingException {
checkLdapConnection(getContextEnv(ldapUrl, ldapBindDn, ldapBindPwd, authenticate));
}
/**
* @param contextEnv Environment properties to build a {@link InitialDirContext}
* @since 6.0
*/
public void checkLdapConnection(Hashtable<Object, Object> contextEnv) throws NamingException {
DirContext dirContext = new InitialDirContext(contextEnv);
dirContext.close();
}
/**
* @return a {@link Crypto} instance initialized with the configuration parameters
* @since 7.4
* @see Crypto
*/
public Crypto getCrypto() {
return userConfig.getCrypto();
}
/**
* @param template path to configuration template directory
* @return A {@code nuxeo.defaults} file if it exists.
* @throws ConfigurationException if the template file is not found.
* @since 7.4
*/
public File getTemplateConf(String template) throws ConfigurationException {
File templateDir = new File(template);
if (!templateDir.isAbsolute()) {
templateDir = new File(System.getProperty("user.dir"), template);
if (!templateDir.exists() || !new File(templateDir, NUXEO_DEFAULT_CONF).exists()) {
templateDir = new File(nuxeoDefaultConf.getParentFile(), template);
}
}
if (!templateDir.exists() || !new File(templateDir, NUXEO_DEFAULT_CONF).exists()) {
throw new ConfigurationException("Template not found: " + template);
}
return new File(templateDir, NUXEO_DEFAULT_CONF);
}
/**
* Gets the Java options with 'nuxeo.*' properties substituted. It enables usage of property like ${nuxeo.log.dir}
* inside JAVA_OPTS.
*
* @return the java options string.
*/
protected String getJavaOpts(String key, String value) {
return StrSubstitutor.replace(System.getProperty(key, value), getUserConfig());
}
/**
* @return the value of an environment variable. Overriden for testing.
* @since 9.1
*/
protected String getEnvironmentVariableValue(String key) {
return System.getenv(key);
}
}