001/*
002 * (C) Copyright 2010-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Julien Carsique
018 *
019 */
020
021package org.nuxeo.launcher.config;
022
023import java.io.BufferedReader;
024import java.io.BufferedWriter;
025import java.io.File;
026import java.io.FileInputStream;
027import java.io.FileNotFoundException;
028import java.io.FileReader;
029import java.io.FileWriter;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.StringWriter;
033import java.io.Writer;
034import java.net.Inet6Address;
035import java.net.InetAddress;
036import java.net.MalformedURLException;
037import java.net.ServerSocket;
038import java.net.URL;
039import java.net.URLClassLoader;
040import java.net.UnknownHostException;
041import java.security.MessageDigest;
042import java.sql.Connection;
043import java.sql.Driver;
044import java.sql.DriverManager;
045import java.sql.SQLException;
046import java.util.ArrayList;
047import java.util.Arrays;
048import java.util.Enumeration;
049import java.util.HashMap;
050import java.util.HashSet;
051import java.util.Hashtable;
052import java.util.List;
053import java.util.Map;
054import java.util.Properties;
055import java.util.Set;
056import java.util.StringTokenizer;
057import java.util.TreeSet;
058import java.util.UUID;
059
060import javax.naming.NamingException;
061import javax.naming.directory.DirContext;
062import javax.naming.directory.InitialDirContext;
063
064import org.apache.commons.codec.binary.Hex;
065import org.apache.commons.codec.digest.DigestUtils;
066import org.apache.commons.lang.ArrayUtils;
067import org.apache.commons.lang.StringUtils;
068import org.apache.commons.lang.SystemUtils;
069import org.apache.commons.lang.text.StrSubstitutor;
070import org.apache.commons.logging.Log;
071import org.apache.commons.logging.LogFactory;
072import org.apache.log4j.Logger;
073import org.apache.log4j.helpers.NullEnumeration;
074
075import org.nuxeo.common.Environment;
076import org.nuxeo.common.codec.Crypto;
077import org.nuxeo.common.codec.CryptoProperties;
078import org.nuxeo.common.utils.TextTemplate;
079import org.nuxeo.launcher.commons.DatabaseDriverException;
080import org.nuxeo.launcher.config.JVMVersion.UpTo;
081import org.nuxeo.log4j.Log4JHelper;
082
083import freemarker.core.ParseException;
084import freemarker.template.TemplateException;
085
086/**
087 * Builder for server configuration and datasource files from templates and properties.
088 *
089 * @author jcarsique
090 */
091public class ConfigurationGenerator {
092
093    /**
094     * @since 6.0
095     */
096    public static final String TEMPLATE_SEPARATOR = ",";
097
098    /**
099     * Accurate but not used internally. NXP-18023: Java 8 update 40+ required
100     *
101     * @since 5.7
102     */
103    public static final String[] COMPLIANT_JAVA_VERSIONS = new String[] { "1.8.0_40" };
104
105    /**
106     * @since 5.6
107     */
108    protected static final String CONFIGURATION_PROPERTIES = "configuration.properties";
109
110    private static final Log log = LogFactory.getLog(ConfigurationGenerator.class);
111
112    /**
113     * @deprecated Since 5.6, use {@link Environment#NUXEO_HOME} instead
114     */
115    @Deprecated
116    public static final String NUXEO_HOME = Environment.NUXEO_HOME;
117
118    public static final String NUXEO_CONF = "nuxeo.conf";
119
120    public static final String TEMPLATES = "templates";
121
122    public static final String NUXEO_DEFAULT_CONF = "nuxeo.defaults";
123
124    /**
125     * Absolute or relative PATH to the user chosen template
126     *
127     * @deprecated use {@link #PARAM_TEMPLATES_NAME} instead
128     */
129    @Deprecated
130    public static final String PARAM_TEMPLATE_NAME = "nuxeo.template";
131
132    /**
133     * Absolute or relative PATH to the user chosen templates (comma separated list)
134     */
135    public static final String PARAM_TEMPLATES_NAME = "nuxeo.templates";
136
137    public static final String PARAM_TEMPLATE_DBNAME = "nuxeo.dbtemplate";
138
139    /**
140     * @since 8.1
141     */
142    public static final String PARAM_TEMPLATE_DBNOSQL_NAME = "nuxeo.dbnosqltemplate";
143
144    public static final String PARAM_TEMPLATE_DBTYPE = "nuxeo.db.type";
145
146    /**
147     * @since 8.1
148     */
149    public static final String PARAM_TEMPLATE_DBNOSQL_TYPE = "nuxeo.dbnosql.type";
150
151    /**
152     * @deprecated since 5.7
153     */
154    @Deprecated
155    public static final String PARAM_TEMPLATES_NODB = "nuxeo.nodbtemplates";
156
157    public static final String OLD_PARAM_TEMPLATES_PARSING_EXTENSIONS = "nuxeo.templates.parsing.extensions";
158
159    public static final String PARAM_TEMPLATES_PARSING_EXTENSIONS = "nuxeo.plaintext_parsing_extensions";
160
161    public static final String PARAM_TEMPLATES_FREEMARKER_EXTENSIONS = "nuxeo.freemarker_parsing_extensions";
162
163    /**
164     * Absolute or relative PATH to the included templates (comma separated list)
165     */
166    protected static final String PARAM_INCLUDED_TEMPLATES = "nuxeo.template.includes";
167
168    public static final String PARAM_FORCE_GENERATION = "nuxeo.force.generation";
169
170    public static final String BOUNDARY_BEGIN = "### BEGIN - DO NOT EDIT BETWEEN BEGIN AND END ###";
171
172    public static final String BOUNDARY_END = "### END - DO NOT EDIT BETWEEN BEGIN AND END ###";
173
174    public static final List<String> DB_LIST = Arrays.asList("default", "postgresql", "oracle", "mysql", "mssql", "db2");
175
176    public static final List<String> DB_NOSQL_LIST = Arrays.asList("none", "mongodb", "marklogic");
177
178    public static final List<String> DB_EXCLUDE_CHECK_LIST = Arrays.asList("default", "none");
179
180    public static final String PARAM_WIZARD_DONE = "nuxeo.wizard.done";
181
182    public static final String PARAM_WIZARD_RESTART_PARAMS = "wizard.restart.params";
183
184    public static final String PARAM_FAKE_WINDOWS = "org.nuxeo.fake.vindoz";
185
186    public static final String PARAM_LOOPBACK_URL = "nuxeo.loopback.url";
187
188    public static final int MIN_PORT = 1;
189
190    public static final int MAX_PORT = 65535;
191
192    public static final int ADDRESS_PING_TIMEOUT = 1000;
193
194    public static final String PARAM_BIND_ADDRESS = "nuxeo.bind.address";
195
196    public static final String PARAM_HTTP_PORT = "nuxeo.server.http.port";
197
198    /**
199     * @deprecated Since 7.4. Use {@link Environment#SERVER_STATUS_KEY} instead
200     */
201    @Deprecated
202    public static final String PARAM_STATUS_KEY = Environment.SERVER_STATUS_KEY;
203
204    public static final String PARAM_CONTEXT_PATH = "org.nuxeo.ecm.contextPath";
205
206    public static final String PARAM_MP_DIR = "nuxeo.distribution.marketplace.dir";
207
208    public static final String DISTRIBUTION_MP_DIR = "setupWizardDownloads";
209
210    public static final String INSTALL_AFTER_RESTART = "installAfterRestart.log";
211
212    public static final String PARAM_DB_DRIVER = "nuxeo.db.driver";
213
214    public static final String PARAM_DB_JDBC_URL = "nuxeo.db.jdbc.url";
215
216    public static final String PARAM_DB_HOST = "nuxeo.db.host";
217
218    public static final String PARAM_DB_PORT = "nuxeo.db.port";
219
220    public static final String PARAM_DB_NAME = "nuxeo.db.name";
221
222    public static final String PARAM_DB_USER = "nuxeo.db.user";
223
224    public static final String PARAM_DB_PWD = "nuxeo.db.password";
225
226    /**
227     * @since 8.1
228     */
229    public static final String PARAM_MONGODB_NAME = "nuxeo.mongodb.dbname";
230
231    /**
232     * @since 8.1
233     */
234    public static final String PARAM_MONGODB_SERVER = "nuxeo.mongodb.server";
235
236    /**
237     * Keys which value must be displayed thoughtfully
238     *
239     * @since 8.1
240     */
241    public static final List<String> SECRET_KEYS = Arrays.asList(PARAM_DB_PWD, "mailservice.password",
242            "mail.transport.password", "nuxeo.http.proxy.password", "nuxeo.ldap.bindpassword",
243            "nuxeo.user.emergency.password");
244
245    /**
246     * @deprecated Since 7.10. Use {@link Environment#PRODUCT_NAME}
247     */
248    @Deprecated
249    public static final String PARAM_PRODUCT_NAME = Environment.PRODUCT_NAME;
250
251    /**
252     * @deprecated Since 7.10. Use {@link Environment#PRODUCT_VERSION}
253     */
254    @Deprecated
255    public static final String PARAM_PRODUCT_VERSION = Environment.PRODUCT_VERSION;
256
257    /**
258     * @since 5.6
259     */
260    public static final String PARAM_NUXEO_URL = "nuxeo.url";
261
262    /**
263     * Global dev property, duplicated from runtime framework
264     *
265     * @since 5.6
266     */
267    public static final String NUXEO_DEV_SYSTEM_PROP = "org.nuxeo.dev";
268
269    /**
270     * Seam hot reload property, also controlled by {@link #NUXEO_DEV_SYSTEM_PROP}
271     *
272     * @since 5.6
273     */
274    public static final String SEAM_DEBUG_SYSTEM_PROP = "org.nuxeo.seam.debug";
275
276    /**
277     * Old way of detecting if seam debug should be enabled, by looking for the presence of this file. Setting property
278     * {@link #SEAM_DEBUG_SYSTEM_PROP} in nuxeo.conf is enough now.
279     *
280     * @deprecated since 5.6
281     * @since 5.6
282     */
283    @Deprecated
284    public static final String SEAM_HOT_RELOAD_GLOBAL_CONFIG_FILE = "seam-debug.properties";
285
286    /** @since 8.4 */
287    public static final String JVMCHECK_PROP = "jvmcheck";
288
289    /** @since 8.4 */
290    public static final String JVMCHECK_FAIL = "fail";
291
292    /** @since 8.4 */
293    public static final String JVMCHECK_NOFAIL = "nofail";
294
295    private final File nuxeoHome;
296
297    // User configuration file
298    private final File nuxeoConf;
299
300    // Chosen templates
301    private final List<File> includedTemplates = new ArrayList<>();
302
303    // Common default configuration file
304    private File nuxeoDefaultConf;
305
306    public boolean isJBoss;
307
308    public boolean isJetty;
309
310    public boolean isTomcat;
311
312    private ServerConfigurator serverConfigurator;
313
314    private boolean forceGeneration;
315
316    private Properties defaultConfig;
317
318    private CryptoProperties userConfig;
319
320    private boolean configurable = false;
321
322    private boolean onceGeneration = false;
323
324    private String templates;
325
326    // if PARAM_FORCE_GENERATION=once, set to false; else keep current value
327    private boolean setOnceToFalse = true;
328
329    // if PARAM_FORCE_GENERATION=false, set to once; else keep the current
330    // value
331    private boolean setFalseToOnce = false;
332
333    public boolean isConfigurable() {
334        return configurable;
335    }
336
337    public ConfigurationGenerator() {
338        this(true, false);
339    }
340
341    private boolean quiet = false;
342
343    @SuppressWarnings("unused")
344    private boolean debug = false;
345
346    private static boolean hideDeprecationWarnings = false;
347
348    private Environment env;
349
350    private Properties storedConfig;
351
352    private String currentConfigurationDigest;
353
354    /**
355     * @since 5.7
356     */
357    protected Properties getStoredConfig() {
358        if (storedConfig == null) {
359            updateStoredConfig();
360        }
361        return storedConfig;
362    }
363
364    protected static final Map<String, String> parametersMigration = new HashMap<String, String>() {
365        private static final long serialVersionUID = 1L;
366        {
367            put(OLD_PARAM_TEMPLATES_PARSING_EXTENSIONS, PARAM_TEMPLATES_PARSING_EXTENSIONS);
368            put("nuxeo.db.user.separator.key", "nuxeo.db.user_separator_key");
369            put("mail.pop3.host", "mail.store.host");
370            put("mail.pop3.port", "mail.store.port");
371            put("mail.smtp.host", "mail.transport.host");
372            put("mail.smtp.port", "mail.transport.port");
373            put("mail.smtp.username", "mail.transport.username");
374            put("mail.transport.username", "mail.transport.user");
375            put("mail.smtp.password", "mail.transport.password");
376            put("mail.smtp.usetls", "mail.transport.usetls");
377            put("mail.smtp.auth", "mail.transport.auth");
378        }
379    };
380
381    /**
382     * @param quiet Suppress info level messages from the console output
383     * @param debug Activate debug level logging
384     * @since 5.6
385     */
386    public ConfigurationGenerator(boolean quiet, boolean debug) {
387        this.quiet = quiet;
388        this.debug = debug;
389        File serverHome = Environment.getDefault().getServerHome();
390        if (serverHome != null) {
391            nuxeoHome = serverHome.getAbsoluteFile();
392        } else {
393            File userDir = new File(System.getProperty("user.dir"));
394            if ("bin".equalsIgnoreCase(userDir.getName())) {
395                nuxeoHome = userDir.getParentFile().getAbsoluteFile();
396            } else {
397                nuxeoHome = userDir.getAbsoluteFile();
398            }
399        }
400        String nuxeoConfPath = System.getProperty(NUXEO_CONF);
401        if (nuxeoConfPath != null) {
402            nuxeoConf = new File(nuxeoConfPath).getAbsoluteFile();
403        } else {
404            nuxeoConf = new File(nuxeoHome, "bin" + File.separator + "nuxeo.conf").getAbsoluteFile();
405        }
406        System.setProperty(NUXEO_CONF, nuxeoConf.getPath());
407
408        nuxeoDefaultConf = new File(nuxeoHome, TEMPLATES + File.separator + NUXEO_DEFAULT_CONF);
409
410        // detect server type based on System properties
411        isJetty = System.getProperty(JettyConfigurator.JETTY_HOME) != null;
412        isTomcat = System.getProperty(TomcatConfigurator.TOMCAT_HOME) != null;
413        if (!isJBoss && !isJetty && !isTomcat) {
414            // fallback on jar detection
415            isJBoss = new File(nuxeoHome, "bin/run.jar").exists();
416            isTomcat = new File(nuxeoHome, "bin/bootstrap.jar").exists();
417            String[] files = nuxeoHome.list();
418            for (String file : files) {
419                if (file.startsWith("nuxeo-runtime-launcher")) {
420                    isJetty = true;
421                    break;
422                }
423            }
424        }
425        if (isTomcat) {
426            serverConfigurator = new TomcatConfigurator(this);
427        } else if (isJetty) {
428            serverConfigurator = new JettyConfigurator(this);
429        } else {
430            serverConfigurator = new UnknownServerConfigurator(this);
431        }
432        if (Logger.getRootLogger().getAllAppenders() instanceof NullEnumeration) {
433            serverConfigurator.initLogs();
434        }
435        String homeInfo = "Nuxeo home:          " + nuxeoHome.getPath();
436        String confInfo = "Nuxeo configuration: " + nuxeoConf.getPath();
437        if (quiet) {
438            log.debug(homeInfo);
439            log.debug(confInfo);
440        } else {
441            log.info(homeInfo);
442            log.info(confInfo);
443        }
444    }
445
446    public void hideDeprecationWarnings(boolean hide) {
447        hideDeprecationWarnings = hide;
448    }
449
450    /**
451     * @see #PARAM_FORCE_GENERATION
452     * @param forceGeneration
453     */
454    public void setForceGeneration(boolean forceGeneration) {
455        this.forceGeneration = forceGeneration;
456    }
457
458    /**
459     * @see #PARAM_FORCE_GENERATION
460     * @return true if configuration will be generated from templates
461     * @since 5.4.2
462     */
463    public boolean isForceGeneration() {
464        return forceGeneration;
465    }
466
467    public CryptoProperties getUserConfig() {
468        return userConfig;
469    }
470
471    /**
472     * @since 5.4.2
473     */
474    public final ServerConfigurator getServerConfigurator() {
475        return serverConfigurator;
476    }
477
478    /**
479     * Runs the configuration files generation.
480     */
481    public void run() throws ConfigurationException {
482        if (init()) {
483            if (!serverConfigurator.isConfigured()) {
484                log.info("No current configuration, generating files...");
485                generateFiles();
486            } else if (forceGeneration) {
487                log.info("Configuration files generation (nuxeo.force.generation="
488                        + userConfig.getProperty(PARAM_FORCE_GENERATION) + ")...");
489                generateFiles();
490            } else {
491                log.info("Server already configured (set nuxeo.force.generation=true to force configuration files generation).");
492            }
493        }
494    }
495
496    /**
497     * Initialize configurator, check requirements and load current configuration
498     *
499     * @return returns true if current install is configurable, else returns false
500     */
501    public boolean init() {
502        return init(false);
503    }
504
505    /**
506     * Initialize configurator, check requirements and load current configuration
507     *
508     * @since 5.6
509     * @param forceReload If true, forces configuration reload.
510     * @return returns true if current install is configurable, else returns false
511     */
512    public boolean init(boolean forceReload) {
513        if (serverConfigurator instanceof UnknownServerConfigurator) {
514            configurable = false;
515            forceGeneration = false;
516            log.warn("Server will be considered as not configurable.");
517        }
518        if (!nuxeoConf.exists()) {
519            log.info("Missing " + nuxeoConf);
520            configurable = false;
521            userConfig = new CryptoProperties();
522            defaultConfig = new Properties();
523        } else if (userConfig == null || userConfig.size() == 0 || forceReload) {
524            try {
525                setBasicConfiguration();
526                configurable = true;
527            } catch (ConfigurationException e) {
528                log.warn("Error reading basic configuration.", e);
529                configurable = false;
530            }
531        } else {
532            configurable = true;
533        }
534        return configurable;
535    }
536
537    /**
538     * @param newTemplates
539     * @return Old templates
540     */
541    public String changeTemplates(String newTemplates) {
542        String oldTemplates = templates;
543        templates = newTemplates;
544        try {
545            setBasicConfiguration(false);
546            configurable = true;
547        } catch (ConfigurationException e) {
548            log.warn("Error reading basic configuration.", e);
549            configurable = false;
550        }
551        return oldTemplates;
552    }
553
554    /**
555     * Change templates using given database template
556     *
557     * @param dbTemplate new database template
558     * @since 5.4.2
559     */
560    public void changeDBTemplate(String dbTemplate) {
561        changeTemplates(rebuildTemplatesStr(dbTemplate));
562    }
563
564    private void setBasicConfiguration() throws ConfigurationException {
565        setBasicConfiguration(true);
566    }
567
568    private void setBasicConfiguration(boolean save) throws ConfigurationException {
569        try {
570            // Load default configuration
571            defaultConfig = loadTrimmedProperties(nuxeoDefaultConf);
572            // Add System properties
573            defaultConfig.putAll(System.getProperties());
574            userConfig = new CryptoProperties(defaultConfig);
575
576            // If Windows, replace backslashes in paths in nuxeo.conf
577            if (SystemUtils.IS_OS_WINDOWS) {
578                replaceBackslashes();
579            }
580            // Load user configuration
581            userConfig.putAll(loadTrimmedProperties(nuxeoConf));
582            onceGeneration = "once".equals(userConfig.getProperty(PARAM_FORCE_GENERATION));
583            forceGeneration = onceGeneration
584                    || Boolean.parseBoolean(userConfig.getProperty(PARAM_FORCE_GENERATION, "false"));
585            checkForDeprecatedParameters(userConfig);
586
587            // Synchronize directories between serverConfigurator and
588            // userConfig/defaultConfig
589            setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_DATA_DIR);
590            setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR);
591            setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_PID_DIR);
592            setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_TMP_DIR);
593            setDirectoryWithProperty(org.nuxeo.common.Environment.NUXEO_MP_DIR);
594        } catch (NullPointerException e) {
595            throw new ConfigurationException("Missing file", e);
596        } catch (FileNotFoundException e) {
597            throw new ConfigurationException("Missing file: " + nuxeoDefaultConf + " or " + nuxeoConf, e);
598        } catch (IOException e) {
599            throw new ConfigurationException("Error reading " + nuxeoConf, e);
600        }
601
602        // Override default configuration with specific configuration(s) of
603        // the chosen template(s) which can be outside of server filesystem
604        try {
605            includeTemplates();
606            checkForDeprecatedParameters(defaultConfig);
607            extractDatabaseTemplateName();
608            extractNoSqlDatabaseTemplateName();
609        } catch (FileNotFoundException e) {
610            throw new ConfigurationException("Missing file", e);
611        } catch (IOException e) {
612            throw new ConfigurationException("Error reading " + nuxeoConf, e);
613        }
614
615        Map<String, String> newParametersToSave = evalDynamicProperties();
616        if (save && newParametersToSave != null && !newParametersToSave.isEmpty()) {
617            saveConfiguration(newParametersToSave, false, false);
618        }
619
620        logDebugInformation();
621
622        // Could be useful to initialize DEFAULT env...
623        // initEnv();
624    }
625
626    /**
627     * @since 5.7
628     * @throws IOException
629     */
630    protected void includeTemplates() throws IOException {
631        includedTemplates.clear();
632        List<File> orderedTemplates = includeTemplates(getUserTemplates());
633        includedTemplates.clear();
634        includedTemplates.addAll(orderedTemplates);
635        log.debug(includedTemplates);
636    }
637
638    /**
639     * Old way of detecting if seam debug is set, by checking for the presence of a file.
640     * <p>
641     * On 5.6, using the config generator to get the info from the nuxeo.conf file makes it possible to get the property
642     * value this early, so adding an empty file at {@link #SEAM_HOT_RELOAD_GLOBAL_CONFIG_FILE} is no longer needed.
643     *
644     * @deprecated since 5.6
645     */
646    @Deprecated
647    protected boolean hasSeamDebugFile() {
648        File f = new File(getServerConfigurator().getConfigDir(), SEAM_HOT_RELOAD_GLOBAL_CONFIG_FILE);
649        if (!f.exists()) {
650            return false;
651        }
652        return true;
653    }
654
655    private void logDebugInformation() {
656        String debugPropValue = userConfig.getProperty(NUXEO_DEV_SYSTEM_PROP);
657        if (Boolean.parseBoolean(debugPropValue)) {
658            log.debug("Nuxeo Dev mode enabled");
659        } else {
660            log.debug("Nuxeo Dev mode is not enabled");
661        }
662
663        // XXX: cannot init seam debug mode when global debug mode is set, as
664        // it needs to be activated at startup, and requires the seam-debug jar
665        // to be in the classpath anyway
666        String seamDebugPropValue = userConfig.getProperty(SEAM_DEBUG_SYSTEM_PROP);
667        if (Boolean.parseBoolean(seamDebugPropValue) || hasSeamDebugFile()) {
668            log.debug("Nuxeo Seam HotReload is enabled");
669            // add it to the system props for compat, in case this mode was
670            // detected because of presence of the file in the config dir, and
671            // because it's checked there on code that relies on it
672            System.setProperty(SEAM_DEBUG_SYSTEM_PROP, "true");
673        } else {
674            log.debug("Nuxeo Seam HotReload is not enabled");
675        }
676    }
677
678    /**
679     * Generate properties which values are based on others
680     *
681     * @return Map with new parameters to save in {@code nuxeoConf}
682     * @throws ConfigurationException
683     * @since 5.5
684     */
685    protected HashMap<String, String> evalDynamicProperties() throws ConfigurationException {
686        HashMap<String, String> newParametersToSave = new HashMap<>();
687        evalLoopbackURL();
688        evalServerStatusKey(newParametersToSave);
689        return newParametersToSave;
690    }
691
692    /**
693     * Generate a server status key if not already set
694     *
695     * @param newParametersToSave
696     * @throws ConfigurationException
697     * @see Environment#SERVER_STATUS_KEY
698     * @since 5.5
699     */
700    private void evalServerStatusKey(Map<String, String> newParametersToSave) throws ConfigurationException {
701        if (userConfig.getProperty(Environment.SERVER_STATUS_KEY) == null) {
702            newParametersToSave.put(Environment.SERVER_STATUS_KEY, UUID.randomUUID().toString().substring(0, 8));
703        }
704    }
705
706    private void evalLoopbackURL() throws ConfigurationException {
707        String loopbackURL = userConfig.getProperty(PARAM_LOOPBACK_URL);
708        if (loopbackURL != null) {
709            log.debug("Using configured loop back url: " + loopbackURL);
710            return;
711        }
712        InetAddress bindAddress = getBindAddress();
713        // Address and ports already checked by #checkAddressesAndPorts
714        try {
715            if (bindAddress.isAnyLocalAddress()) {
716                boolean preferIPv6 = "false".equals(System.getProperty("java.net.preferIPv4Stack"))
717                        && "true".equals(System.getProperty("java.net.preferIPv6Addresses"));
718                bindAddress = preferIPv6 ? InetAddress.getByName("::1") : InetAddress.getByName("127.0.0.1");
719                log.debug("Bind address is \"ANY\", using local address instead: " + bindAddress);
720            }
721        } catch (UnknownHostException e) {
722            log.debug(e, e);
723            log.error(e.getMessage());
724        }
725
726        String httpPort = userConfig.getProperty(PARAM_HTTP_PORT);
727        String contextPath = userConfig.getProperty(PARAM_CONTEXT_PATH);
728        // Is IPv6 or IPv4 ?
729        if (bindAddress instanceof Inet6Address) {
730            loopbackURL = "http://[" + bindAddress.getHostAddress() + "]:" + httpPort + contextPath;
731        } else {
732            loopbackURL = "http://" + bindAddress.getHostAddress() + ":" + httpPort + contextPath;
733        }
734        log.debug("Set as loop back URL: " + loopbackURL);
735        defaultConfig.setProperty(PARAM_LOOPBACK_URL, loopbackURL);
736    }
737
738    /**
739     * Read nuxeo.conf, replace backslashes in paths and write new nuxeo.conf
740     *
741     * @throws ConfigurationException if any error reading or writing nuxeo.conf
742     * @since 5.4.1
743     */
744    protected void replaceBackslashes() throws ConfigurationException {
745        StringBuffer sb = new StringBuffer();
746        BufferedReader reader = null;
747        try {
748            reader = new BufferedReader(new FileReader(nuxeoConf));
749            String line;
750            while ((line = reader.readLine()) != null) {
751                if (line.matches(".*:\\\\.*")) {
752                    line = line.replaceAll("\\\\", "/");
753                }
754                sb.append(line + System.getProperty("line.separator"));
755            }
756            reader.close();
757        } catch (IOException e) {
758            throw new ConfigurationException("Error reading " + nuxeoConf, e);
759        } finally {
760            if (reader != null) {
761                try {
762                    reader.close();
763                } catch (IOException e) {
764                    throw new ConfigurationException(e);
765                }
766            }
767        }
768        FileWriter writer = null;
769        try {
770            writer = new FileWriter(nuxeoConf, false);
771            // Copy back file content
772            writer.append(sb.toString());
773        } catch (IOException e) {
774            throw new ConfigurationException("Error writing in " + nuxeoConf, e);
775        } finally {
776            if (writer != null) {
777                try {
778                    writer.close();
779                } catch (IOException e) {
780                    throw new ConfigurationException(e);
781                }
782            }
783        }
784    }
785
786    /**
787     * @since 5.4.2
788     * @param key Directory system key
789     * @see Environment
790     */
791    public void setDirectoryWithProperty(String key) {
792        String directory = userConfig.getProperty(key);
793        if (directory == null) {
794            defaultConfig.setProperty(key, serverConfigurator.getDirectory(key).getPath());
795        } else {
796            serverConfigurator.setDirectory(key, directory);
797        }
798    }
799
800    public String getUserTemplates() {
801        if (templates == null) {
802            templates = userConfig.getProperty(PARAM_TEMPLATES_NAME);
803        }
804        if (templates == null) {
805            // backward compliance: manage parameter for a single template
806            templates = userConfig.getProperty(PARAM_TEMPLATE_NAME);
807        }
808        if (templates == null) {
809            log.warn("No template found in configuration! Fallback on 'default'.");
810            templates = "default";
811        }
812        userConfig.setProperty(PARAM_TEMPLATES_NAME, templates);
813        return templates;
814    }
815
816    protected void generateFiles() throws ConfigurationException {
817        try {
818            serverConfigurator.parseAndCopy(userConfig);
819            serverConfigurator.dumpProperties(userConfig);
820            log.info("Configuration files generated.");
821            // keep true or false, switch once to false
822            if (onceGeneration) {
823                setOnceToFalse = true;
824                writeConfiguration();
825            }
826        } catch (FileNotFoundException e) {
827            throw new ConfigurationException("Missing file: " + e.getMessage(), e);
828        } catch (TemplateException | ParseException e) {
829            throw new ConfigurationException("Could not process FreeMarker template: " + e.getMessage(), e);
830        } catch (IOException e) {
831            throw new ConfigurationException("Configuration failure: " + e.getMessage(), e);
832        }
833    }
834
835    private List<File> includeTemplates(String templatesList) throws IOException {
836        List<File> orderedTemplates = new ArrayList<>();
837        StringTokenizer st = new StringTokenizer(templatesList, TEMPLATE_SEPARATOR);
838        while (st.hasMoreTokens()) {
839            String nextToken = st.nextToken();
840            File chosenTemplate = new File(nextToken);
841            // is it absolute and existing or relative path ?
842            if (!chosenTemplate.exists() || !chosenTemplate.getPath().equals(chosenTemplate.getAbsolutePath())) {
843                chosenTemplate = new File(nuxeoDefaultConf.getParentFile(), nextToken);
844            }
845            if (includedTemplates.contains(chosenTemplate)) {
846                log.debug("Already included " + nextToken);
847                continue;
848            }
849            if (!chosenTemplate.exists()) {
850                log.error(String.format("Template '%s' not found with relative or absolute path (%s). "
851                        + "Check your %s parameter, and %s for included files.", nextToken, chosenTemplate,
852                        PARAM_TEMPLATES_NAME, PARAM_INCLUDED_TEMPLATES));
853                continue;
854            }
855            File chosenTemplateConf = new File(chosenTemplate, NUXEO_DEFAULT_CONF);
856            includedTemplates.add(chosenTemplate);
857            if (!chosenTemplateConf.exists()) {
858                log.warn("Ignore template (no default configuration): " + nextToken);
859                continue;
860            }
861
862            Properties subTemplateConf = loadTrimmedProperties(chosenTemplateConf);
863            String subTemplatesList = subTemplateConf.getProperty(PARAM_INCLUDED_TEMPLATES);
864            if (subTemplatesList != null && subTemplatesList.length() > 0) {
865                orderedTemplates.addAll(includeTemplates(subTemplatesList));
866            }
867            // Load configuration from chosen templates
868            defaultConfig.putAll(subTemplateConf);
869            orderedTemplates.add(chosenTemplate);
870            String templateInfo = "Include template: " + chosenTemplate.getPath();
871            if (quiet) {
872                log.debug(templateInfo);
873            } else {
874                log.info(templateInfo);
875            }
876        }
877        return orderedTemplates;
878    }
879
880    /**
881     * Check for deprecated parameters
882     *
883     * @param properties
884     * @since 5.6
885     */
886    protected void checkForDeprecatedParameters(Properties properties) {
887        serverConfigurator.addServerSpecificParameters(parametersMigration);
888        @SuppressWarnings("rawtypes")
889        Enumeration userEnum = properties.propertyNames();
890        while (userEnum.hasMoreElements()) {
891            String key = (String) userEnum.nextElement();
892            if (parametersMigration.containsKey(key)) {
893                String value = properties.getProperty(key);
894                properties.setProperty(parametersMigration.get(key), value);
895                // Don't remove the deprecated key yet - more
896                // warnings but old things should keep working
897                // properties.remove(key);
898                if (!hideDeprecationWarnings) {
899                    log.warn("Parameter " + key + " is deprecated - please use " + parametersMigration.get(key)
900                            + " instead");
901                }
902            }
903        }
904    }
905
906    public File getNuxeoHome() {
907        return nuxeoHome;
908    }
909
910    public File getNuxeoDefaultConf() {
911        return nuxeoDefaultConf;
912    }
913
914    public List<File> getIncludedTemplates() {
915        return includedTemplates;
916    }
917
918    /**
919     * Save changed parameters in {@code nuxeo.conf}. This method does not check values in map. Use
920     * {@link #saveFilteredConfiguration(Map)} for parameters filtering.
921     *
922     * @param changedParameters Map of modified parameters
923     * @see #saveFilteredConfiguration(Map)
924     */
925    public void saveConfiguration(Map<String, String> changedParameters) throws ConfigurationException {
926        // Keep generation true or once; switch false to once
927        saveConfiguration(changedParameters, false, true);
928    }
929
930    /**
931     * Save changed parameters in {@code nuxeo.conf} calculating templates if changedParameters contains a value for
932     * {@link #PARAM_TEMPLATE_DBNAME}. If a parameter value is empty ("" or null), then the property is unset.
933     * {@link #PARAM_WIZARD_DONE}, {@link #PARAM_TEMPLATES_NAME} and {@link #PARAM_FORCE_GENERATION} cannot be unset,
934     * but their value can be changed.<br/>
935     * This method does not check values in map: use {@link #saveFilteredConfiguration(Map)} for parameters filtering.
936     *
937     * @param changedParameters Map of modified parameters
938     * @param setGenerationOnceToFalse If generation was on (true or once), then set it to false or not?
939     * @param setGenerationFalseToOnce If generation was off (false), then set it to once?
940     * @see #saveFilteredConfiguration(Map)
941     * @since 5.5
942     */
943    public void saveConfiguration(Map<String, String> changedParameters, boolean setGenerationOnceToFalse,
944            boolean setGenerationFalseToOnce) throws ConfigurationException {
945        setOnceToFalse = setGenerationOnceToFalse;
946        setFalseToOnce = setGenerationFalseToOnce;
947        updateStoredConfig();
948        String newDbTemplate = changedParameters.remove(PARAM_TEMPLATE_DBNAME);
949        if (newDbTemplate != null) {
950            changedParameters.put(PARAM_TEMPLATES_NAME, rebuildTemplatesStr(newDbTemplate));
951        }
952        newDbTemplate = changedParameters.remove(PARAM_TEMPLATE_DBNOSQL_NAME);
953        if (newDbTemplate != null) {
954            changedParameters.put(PARAM_TEMPLATES_NAME, rebuildTemplatesStr(newDbTemplate));
955        }
956        if (changedParameters.containsValue(null) || changedParameters.containsValue("")) {
957            // There are properties to unset
958            Set<String> propertiesToUnset = new HashSet<>();
959            for (String key : changedParameters.keySet()) {
960                if (StringUtils.isEmpty(changedParameters.get(key))) {
961                    propertiesToUnset.add(key);
962                }
963            }
964            for (String key : propertiesToUnset) {
965                changedParameters.remove(key);
966                userConfig.remove(key);
967            }
968        }
969        userConfig.putAll(changedParameters);
970        writeConfiguration();
971        updateStoredConfig();
972    }
973
974    private void updateStoredConfig() {
975        if (storedConfig == null) {
976            storedConfig = new Properties(defaultConfig);
977        } else {
978            storedConfig.clear();
979        }
980        storedConfig.putAll(userConfig);
981    }
982
983    /**
984     * Save changed parameters in {@code nuxeo.conf}, filtering parameters with {@link #getChangedParameters(Map)}
985     *
986     * @param changedParameters Maps of modified parameters
987     * @since 5.4.2
988     * @see #saveConfiguration(Map)
989     * @see #getChangedParameters(Map)
990     */
991    public void saveFilteredConfiguration(Map<String, String> changedParameters) throws ConfigurationException {
992        Map<String, String> filteredParameters = getChangedParameters(changedParameters);
993        saveConfiguration(filteredParameters);
994    }
995
996    /**
997     * Filters given parameters including them only if (there was no previous value and new value is not empty/null) or
998     * (there was a previous value and it differs from the new value)
999     *
1000     * @param changedParameters parameters to be filtered
1001     * @return filtered map
1002     * @since 5.4.2
1003     */
1004    public Map<String, String> getChangedParameters(Map<String, String> changedParameters) {
1005        Map<String, String> filteredChangedParameters = new HashMap<>();
1006        for (String key : changedParameters.keySet()) {
1007            String oldParam = getStoredConfig().getProperty(key);
1008            String newParam = changedParameters.get(key);
1009            if (newParam != null) {
1010                newParam = newParam.trim();
1011            }
1012            if (oldParam == null && StringUtils.isNotEmpty(newParam) || oldParam != null
1013                    && !oldParam.trim().equals(newParam)) {
1014                filteredChangedParameters.put(key, newParam);
1015            }
1016        }
1017        return filteredChangedParameters;
1018    }
1019
1020    private void writeConfiguration() throws ConfigurationException {
1021        final MessageDigest newContentDigest = DigestUtils.getMd5Digest();
1022        StringWriter newContent = new StringWriter() {
1023            @Override
1024            public void write(String str) {
1025                if (str != null) {
1026                    newContentDigest.update(str.getBytes());
1027                }
1028                super.write(str);
1029            }
1030        };
1031        // Copy back file content
1032        newContent.append(readConfiguration());
1033        // Write changed parameters
1034        newContent.write(BOUNDARY_BEGIN + System.getProperty("line.separator"));
1035        for (Object o : new TreeSet<>(userConfig.keySet())) {
1036            String key = (String) o;
1037            // Ignore parameters already stored in newContent
1038            if (PARAM_FORCE_GENERATION.equals(key) || PARAM_WIZARD_DONE.equals(key) || PARAM_TEMPLATES_NAME.equals(key)) {
1039                continue;
1040            }
1041            String oldValue = storedConfig.getProperty(key, "");
1042            String newValue = userConfig.getRawProperty(key, "");
1043            if (!newValue.equals(oldValue)) {
1044                newContent.write("#" + key + "=" + oldValue + System.getProperty("line.separator"));
1045                newContent.write(key + "=" + newValue + System.getProperty("line.separator"));
1046            }
1047        }
1048        newContent.write(BOUNDARY_END + System.getProperty("line.separator"));
1049
1050        // Write file only if content has changed
1051        if (!Hex.encodeHexString(newContentDigest.digest()).equals(currentConfigurationDigest)) {
1052            try (Writer writer = new FileWriter(nuxeoConf, false)) {
1053                writer.append(newContent.getBuffer());
1054            } catch (IOException e) {
1055                throw new ConfigurationException("Error writing in " + nuxeoConf, e);
1056            }
1057        }
1058    }
1059
1060    private StringBuffer readConfiguration() throws ConfigurationException {
1061        String wizardParam = null, templatesParam = null;
1062        Integer generationIndex = null, wizardIndex = null, templatesIndex = null;
1063        // Will change wizardParam value instead of appending it
1064        wizardParam = userConfig.getProperty(PARAM_WIZARD_DONE);
1065        // Will change templatesParam value instead of appending it
1066        templatesParam = userConfig.getProperty(PARAM_TEMPLATES_NAME);
1067        ArrayList<String> newLines = new ArrayList<>();
1068        try (BufferedReader reader = new BufferedReader(new FileReader(nuxeoConf))) {
1069            String line;
1070            MessageDigest digest = DigestUtils.getMd5Digest();
1071            boolean onConfiguratorContent = false;
1072            while ((line = reader.readLine()) != null) {
1073                digest.update(line.getBytes());
1074                if (!onConfiguratorContent) {
1075                    if (!line.startsWith(BOUNDARY_BEGIN)) {
1076                        if (line.startsWith(PARAM_FORCE_GENERATION)) {
1077                            if (setOnceToFalse && onceGeneration) {
1078                                line = PARAM_FORCE_GENERATION + "=false";
1079                            }
1080                            if (setFalseToOnce && !forceGeneration) {
1081                                line = PARAM_FORCE_GENERATION + "=once";
1082                            }
1083                            if (generationIndex == null) {
1084                                newLines.add(line);
1085                                generationIndex = newLines.size() - 1;
1086                            } else {
1087                                newLines.set(generationIndex, line);
1088                            }
1089                        } else if (line.startsWith(PARAM_WIZARD_DONE)) {
1090                            if (wizardParam != null) {
1091                                line = PARAM_WIZARD_DONE + "=" + wizardParam;
1092                            }
1093                            if (wizardIndex == null) {
1094                                newLines.add(line);
1095                                wizardIndex = newLines.size() - 1;
1096                            } else {
1097                                newLines.set(wizardIndex, line);
1098                            }
1099                        } else if (line.startsWith(PARAM_TEMPLATES_NAME)) {
1100                            if (templatesParam != null) {
1101                                line = PARAM_TEMPLATES_NAME + "=" + templatesParam;
1102                            }
1103                            if (templatesIndex == null) {
1104                                newLines.add(line);
1105                                templatesIndex = newLines.size() - 1;
1106                            } else {
1107                                newLines.set(templatesIndex, line);
1108                            }
1109                        } else {
1110                            int equalIdx = line.indexOf("=");
1111                            if (equalIdx < 1 || line.trim().startsWith("#")) {
1112                                newLines.add(line);
1113                            } else {
1114                                String key = line.substring(0, equalIdx).trim();
1115                                if (userConfig.getProperty(key) != null) {
1116                                    newLines.add(line);
1117                                } else {
1118                                    newLines.add("#" + line);
1119                                }
1120                            }
1121                        }
1122                    } else {
1123                        // What must be written just before the BOUNDARY_BEGIN
1124                        if (templatesIndex == null && templatesParam != null) {
1125                            newLines.add(PARAM_TEMPLATES_NAME + "=" + templatesParam);
1126                            templatesIndex = newLines.size() - 1;
1127                        }
1128                        if (wizardIndex == null && wizardParam != null) {
1129                            newLines.add(PARAM_WIZARD_DONE + "=" + wizardParam);
1130                            wizardIndex = newLines.size() - 1;
1131                        }
1132                        onConfiguratorContent = true;
1133                    }
1134                } else {
1135                    if (!line.startsWith(BOUNDARY_END)) {
1136                        int equalIdx = line.indexOf("=");
1137                        if (line.startsWith("#" + PARAM_TEMPLATES_NAME) || line.startsWith(PARAM_TEMPLATES_NAME)) {
1138                            // Backward compliance, it must be ignored
1139                            continue;
1140                        }
1141                        if (equalIdx < 1) { // Ignore non-readable lines
1142                            continue;
1143                        }
1144                        if (line.trim().startsWith("#")) {
1145                            String key = line.substring(1, equalIdx).trim();
1146                            String value = line.substring(equalIdx + 1).trim();
1147                            getStoredConfig().setProperty(key, value);
1148                        } else {
1149                            String key = line.substring(0, equalIdx).trim();
1150                            String value = line.substring(equalIdx + 1).trim();
1151                            if (!value.equals(userConfig.getRawProperty(key))) {
1152                                getStoredConfig().setProperty(key, value);
1153                            }
1154                        }
1155                    } else {
1156                        onConfiguratorContent = false;
1157                    }
1158                }
1159            }
1160            reader.close();
1161            currentConfigurationDigest = Hex.encodeHexString(digest.digest());
1162        } catch (IOException e) {
1163            throw new ConfigurationException("Error reading " + nuxeoConf, e);
1164        }
1165        StringBuffer newContent = new StringBuffer();
1166        for (int i = 0; i < newLines.size(); i++) {
1167            newContent.append(newLines.get(i).trim() + System.getProperty("line.separator"));
1168        }
1169        return newContent;
1170    }
1171
1172    /**
1173     * Extract a database template from the current list of templates. Return the last one if there are multiples.
1174     *
1175     * @see #rebuildTemplatesStr(String)
1176     */
1177    public String extractDatabaseTemplateName() {
1178        return extractDbTemplateName(DB_LIST, PARAM_TEMPLATE_DBTYPE, PARAM_TEMPLATE_DBNAME, "unknown");
1179    }
1180
1181    /**
1182     * Extract a NoSQL database template from the current list of templates. Return the last one if there are multiples.
1183     *
1184     * @see #rebuildTemplatesStr(String)
1185     * @since 8.1
1186     */
1187    public String extractNoSqlDatabaseTemplateName() {
1188        return extractDbTemplateName(DB_NOSQL_LIST, PARAM_TEMPLATE_DBNOSQL_TYPE, PARAM_TEMPLATE_DBNOSQL_NAME, null);
1189    }
1190
1191    private String extractDbTemplateName(List<String> knownDbList, String paramTemplateDbType,
1192            String paramTemplateDbName, String defaultTemplate) {
1193        String dbTemplate = defaultTemplate;
1194        boolean found = false;
1195        for (File templateFile : includedTemplates) {
1196            String template = templateFile.getName();
1197            if (knownDbList.contains(template)) {
1198                dbTemplate = template;
1199                found = true;
1200            }
1201        }
1202        String dbType = userConfig.getProperty(paramTemplateDbType);
1203        if (!found && dbType != null) {
1204            log.warn(String.format("Didn't find a known database template in the list but "
1205                    + "some template contributed a value for %s.", paramTemplateDbType));
1206            dbTemplate = dbType;
1207        }
1208        if (dbTemplate != null && !dbTemplate.equals(dbType)) {
1209            if (dbType == null) {
1210                log.warn(String.format("Missing value for %s, using %s", paramTemplateDbType, dbTemplate));
1211                userConfig.setProperty(paramTemplateDbType, dbTemplate);
1212            } else {
1213                log.error(String.format("Inconsistent values between %s (%s) and %s (%s)", paramTemplateDbName,
1214                        dbTemplate, paramTemplateDbType, dbType));
1215            }
1216        }
1217        if (dbTemplate == null) {
1218            defaultConfig.remove(paramTemplateDbName);
1219        } else {
1220            defaultConfig.setProperty(paramTemplateDbName, dbTemplate);
1221        }
1222        return dbTemplate;
1223    }
1224
1225    /**
1226     * @return nuxeo.conf file used
1227     */
1228    public File getNuxeoConf() {
1229        return nuxeoConf;
1230    }
1231
1232    /**
1233     * Delegate logs initialization to serverConfigurator instance
1234     *
1235     * @since 5.4.2
1236     */
1237    public void initLogs() {
1238        serverConfigurator.initLogs();
1239    }
1240
1241    /**
1242     * @return log directory
1243     * @since 5.4.2
1244     */
1245    public File getLogDir() {
1246        return serverConfigurator.getLogDir();
1247    }
1248
1249    /**
1250     * @return pid directory
1251     * @since 5.4.2
1252     */
1253    public File getPidDir() {
1254        return serverConfigurator.getPidDir();
1255    }
1256
1257    /**
1258     * @return Data directory
1259     * @since 5.4.2
1260     */
1261    public File getDataDir() {
1262        return serverConfigurator.getDataDir();
1263    }
1264
1265    /**
1266     * Create needed directories. Check existence of old paths. If old paths have been found and they cannot be upgraded
1267     * automatically, then upgrading message is logged and error thrown.
1268     *
1269     * @throws ConfigurationException If a deprecated directory has been detected.
1270     * @since 5.4.2
1271     * @see ServerConfigurator#verifyInstallation()
1272     */
1273    public void verifyInstallation() throws ConfigurationException {
1274        checkJavaVersion();
1275        ifNotExistsAndIsDirectoryThenCreate(getLogDir());
1276        ifNotExistsAndIsDirectoryThenCreate(getPidDir());
1277        ifNotExistsAndIsDirectoryThenCreate(getDataDir());
1278        ifNotExistsAndIsDirectoryThenCreate(getTmpDir());
1279        ifNotExistsAndIsDirectoryThenCreate(getPackagesDir());
1280        checkAddressesAndPorts();
1281        serverConfigurator.verifyInstallation();
1282        if (!DB_EXCLUDE_CHECK_LIST.contains(userConfig.getProperty(PARAM_TEMPLATE_DBTYPE))) {
1283            try {
1284                checkDatabaseConnection(userConfig.getProperty(PARAM_TEMPLATE_DBNAME),
1285                        userConfig.getProperty(PARAM_DB_NAME), userConfig.getProperty(PARAM_DB_USER),
1286                        userConfig.getProperty(PARAM_DB_PWD), userConfig.getProperty(PARAM_DB_HOST),
1287                        userConfig.getProperty(PARAM_DB_PORT));
1288            } catch (IOException e) {
1289                throw new ConfigurationException(e);
1290            } catch (DatabaseDriverException e) {
1291                log.debug(e, e);
1292                log.error(e.getMessage());
1293                throw new ConfigurationException("Could not find database driver: " + e.getMessage());
1294            } catch (SQLException e) {
1295                log.debug(e, e);
1296                log.error(e.getMessage());
1297                throw new ConfigurationException("Failed to connect on database: " + e.getMessage());
1298            }
1299        }
1300        // TODO NXP-18773: check NoSQL database
1301    }
1302
1303    /**
1304     * @return Marketplace packages directory
1305     * @since 5.9.4
1306     */
1307    private File getPackagesDir() {
1308        return serverConfigurator.getPackagesDir();
1309    }
1310
1311    /**
1312     * Check that the process is executed with a supported Java version. See <a
1313     * href="http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html">J2SE SDK/JRE Version String
1314     * Naming Convention</a>
1315     *
1316     * @throws ConfigurationException
1317     * @since 5.6
1318     */
1319    public void checkJavaVersion() throws ConfigurationException {
1320        String requiredVersion = COMPLIANT_JAVA_VERSIONS[0];
1321        String version = System.getProperty("java.version");
1322        if (!checkJavaVersion(version, requiredVersion, true, true)) {
1323            String message = String.format("Nuxeo requires Java %s+ (detected %s).", requiredVersion, version);
1324            throw new ConfigurationException(message + " See '" + JVMCHECK_PROP + "' option to bypass version check.");
1325        }
1326    }
1327
1328    /**
1329     * Checks the java version compared to the required one.
1330     * <p>
1331     * Loose compliance is assumed if the major version is greater than the required major version or a jvmcheck=nofail
1332     * flag is set.
1333     *
1334     * @param version the java version
1335     * @param requiredVersion the required java version
1336     * @param allowNoFailFlag if {@code true} then check jvmcheck=nofail flag to always have loose compliance
1337     * @param warnIfLooseCompliance if {@code true} then log a WARN if the is loose compliance
1338     * @return true if the java version is compliant (maybe loosely) with the required version
1339     * @since 8.4
1340     */
1341    protected static boolean checkJavaVersion(String version, String requiredVersion, boolean allowNoFailFlag,
1342            boolean warnIfLooseCompliance) {
1343        allowNoFailFlag = allowNoFailFlag
1344                && JVMCHECK_NOFAIL.equalsIgnoreCase(System.getProperty(JVMCHECK_PROP, JVMCHECK_FAIL));
1345        try {
1346            JVMVersion required = JVMVersion.parse(requiredVersion);
1347            JVMVersion actual = JVMVersion.parse(version);
1348            boolean compliant = actual.compareTo(required) >= 0;
1349            if (compliant && actual.compareTo(required, UpTo.MAJOR) == 0) {
1350                return true;
1351            }
1352            if (!compliant && !allowNoFailFlag) {
1353                return false;
1354            }
1355            log.warn(String.format("Nuxeo requires Java %s+ (detected %s).", requiredVersion, version));
1356            return true;
1357        } catch (java.text.ParseException cause) {
1358            if (allowNoFailFlag) {
1359                log.warn("Cannot check java version", cause);
1360                return true;
1361            }
1362            throw new IllegalArgumentException("Cannot check java version", cause);
1363        }
1364    }
1365
1366    /**
1367     * Checks the java version compared to the required one.
1368     * <p>
1369     * If major version is same as required major version and minor is greater or equal, it is compliant.
1370     * <p>
1371     * If major version is greater than required major version, it is compliant.
1372     *
1373     * @param version the java version
1374     * @param requiredVersion the required java version
1375     * @return true if the java version is compliant with the required version
1376     * @since 8.4
1377     */
1378    public static boolean checkJavaVersion(String version, String requiredVersion) {
1379        return checkJavaVersion(version, requiredVersion, false, false);
1380    }
1381
1382    /**
1383     * Will check the configured addresses are reachable and Nuxeo required ports are available on those addresses.
1384     * Server specific implementations should override this method in order to check for server specific ports.
1385     * {@link #PARAM_BIND_ADDRESS} must be set before.
1386     *
1387     * @throws ConfigurationException
1388     * @since 5.5
1389     * @see ServerConfigurator#verifyInstallation()
1390     */
1391    public void checkAddressesAndPorts() throws ConfigurationException {
1392        InetAddress bindAddress = getBindAddress();
1393        // Sanity check
1394        if (bindAddress.isMulticastAddress()) {
1395            throw new ConfigurationException("Multicast address won't work: " + bindAddress);
1396        }
1397        checkAddressReachable(bindAddress);
1398        checkPortAvailable(bindAddress, Integer.parseInt(userConfig.getProperty(PARAM_HTTP_PORT)));
1399    }
1400
1401    public InetAddress getBindAddress() throws ConfigurationException {
1402        InetAddress bindAddress;
1403        try {
1404            bindAddress = InetAddress.getByName(userConfig.getProperty(PARAM_BIND_ADDRESS));
1405            log.debug("Configured bind address: " + bindAddress);
1406        } catch (UnknownHostException e) {
1407            throw new ConfigurationException(e);
1408        }
1409        return bindAddress;
1410    }
1411
1412    /**
1413     * @param address address to check for availability
1414     * @throws ConfigurationException
1415     * @since 5.5
1416     */
1417    public static void checkAddressReachable(InetAddress address) throws ConfigurationException {
1418        try {
1419            log.debug("Checking availability of " + address);
1420            address.isReachable(ADDRESS_PING_TIMEOUT);
1421        } catch (IOException e) {
1422            throw new ConfigurationException("Unreachable bind address " + address);
1423        }
1424    }
1425
1426    /**
1427     * Checks if port is available on given address.
1428     *
1429     * @param port port to check for availability
1430     * @throws ConfigurationException Throws an exception if address is unavailable.
1431     * @since 5.5
1432     */
1433    public static void checkPortAvailable(InetAddress address, int port) throws ConfigurationException {
1434        if ((port == 0) || (port == -1)) {
1435            log.warn("Port is set to " + Integer.toString(port)
1436                    + " - assuming it is disabled - skipping availability check");
1437            return;
1438        }
1439        if (port < MIN_PORT || port > MAX_PORT) {
1440            throw new IllegalArgumentException("Invalid port: " + port);
1441        }
1442        ServerSocket socketTCP = null;
1443        // DatagramSocket socketUDP = null;
1444        try {
1445            log.debug("Checking availability of port " + port + " on address " + address);
1446            socketTCP = new ServerSocket(port, 0, address);
1447            socketTCP.setReuseAddress(true);
1448            // socketUDP = new DatagramSocket(port, address);
1449            // socketUDP.setReuseAddress(true);
1450            // return true;
1451        } catch (IOException e) {
1452            throw new ConfigurationException(e.getMessage() + ": " + address + ":" + port, e);
1453        } finally {
1454            // if (socketUDP != null) {
1455            // socketUDP.close();
1456            // }
1457            if (socketTCP != null) {
1458                try {
1459                    socketTCP.close();
1460                } catch (IOException e) {
1461                    // Do not throw
1462                }
1463            }
1464        }
1465    }
1466
1467    /**
1468     * @return Temporary directory
1469     */
1470    public File getTmpDir() {
1471        return serverConfigurator.getTmpDir();
1472    }
1473
1474    private void ifNotExistsAndIsDirectoryThenCreate(File directory) {
1475        if (!directory.isDirectory()) {
1476            directory.mkdirs();
1477        }
1478    }
1479
1480    /**
1481     * @return Log files produced by Log4J configuration without loading this configuration instead of current active
1482     *         one.
1483     * @since 5.4.2
1484     */
1485    public ArrayList<String> getLogFiles() {
1486        File log4jConfFile = serverConfigurator.getLogConfFile();
1487        System.setProperty(org.nuxeo.common.Environment.NUXEO_LOG_DIR, getLogDir().getPath());
1488        return Log4JHelper.getFileAppendersFiles(log4jConfFile);
1489    }
1490
1491    /**
1492     * Check if wizard must and can be ran
1493     *
1494     * @return true if configuration wizard is required before starting Nuxeo
1495     * @since 5.4.2
1496     */
1497    public boolean isWizardRequired() {
1498        return !"true".equalsIgnoreCase(getUserConfig().getProperty(PARAM_WIZARD_DONE, "true"))
1499                && serverConfigurator.isWizardAvailable();
1500    }
1501
1502    /**
1503     * Rebuild a templates string for use in nuxeo.conf
1504     *
1505     * @param dbTemplate database template to use instead of current one
1506     * @return new templates string using given dbTemplate
1507     * @since 5.4.2
1508     * @see #extractDatabaseTemplateName()
1509     * @see #changeDBTemplate(String)
1510     * @see #changeTemplates(String)
1511     */
1512    public String rebuildTemplatesStr(String dbTemplate) {
1513        List<String> templatesList = new ArrayList<>();
1514        templatesList.addAll(Arrays.asList(templates.split(TEMPLATE_SEPARATOR)));
1515        String currentDBTemplate = null;
1516        if (DB_LIST.contains(dbTemplate)) {
1517            currentDBTemplate = userConfig.getProperty(PARAM_TEMPLATE_DBNAME);
1518            if (currentDBTemplate == null) {
1519                currentDBTemplate = extractDatabaseTemplateName();
1520            }
1521        } else if (DB_NOSQL_LIST.contains(dbTemplate)) {
1522            currentDBTemplate = userConfig.getProperty(PARAM_TEMPLATE_DBNOSQL_NAME);
1523            if (currentDBTemplate == null) {
1524                currentDBTemplate = extractNoSqlDatabaseTemplateName();
1525            }
1526            if ("none".equals(dbTemplate)) {
1527                dbTemplate = null;
1528            }
1529        }
1530        int dbIdx = templatesList.indexOf(currentDBTemplate);
1531        if (dbIdx < 0) {
1532            if (dbTemplate == null) {
1533                return templates;
1534            }
1535            // current db template is implicit => set the new one
1536            templatesList.add(dbTemplate);
1537        } else if (dbTemplate == null) {
1538            // current db template is explicit => remove it
1539            templatesList.remove(dbIdx);
1540        } else {
1541            // current db template is explicit => replace it
1542            templatesList.set(dbIdx, dbTemplate);
1543        }
1544        return StringUtils.join(templatesList, TEMPLATE_SEPARATOR);
1545    }
1546
1547    /**
1548     * @return Nuxeo config directory
1549     * @since 5.4.2
1550     */
1551    public File getConfigDir() {
1552        return serverConfigurator.getConfigDir();
1553    }
1554
1555    /**
1556     * Ensure the server will start only wizard application, not Nuxeo
1557     *
1558     * @since 5.4.2
1559     */
1560    public void prepareWizardStart() {
1561        serverConfigurator.prepareWizardStart();
1562    }
1563
1564    /**
1565     * Ensure the wizard won't be started and nuxeo is ready for use
1566     *
1567     * @since 5.4.2
1568     */
1569    public void cleanupPostWizard() {
1570        serverConfigurator.cleanupPostWizard();
1571    }
1572
1573    /**
1574     * @return Nuxeo runtime home
1575     */
1576    public File getRuntimeHome() {
1577        return serverConfigurator.getRuntimeHome();
1578    }
1579
1580    /**
1581     * @since 5.4.2
1582     * @return true if there's an install in progress
1583     */
1584    public boolean isInstallInProgress() {
1585        return getInstallFile().exists();
1586    }
1587
1588    /**
1589     * @return File pointing to the directory containing the marketplace packages included in the distribution
1590     * @since 5.6
1591     */
1592    public File getDistributionMPDir() {
1593        String mpDir = userConfig.getProperty(PARAM_MP_DIR, DISTRIBUTION_MP_DIR);
1594        return new File(getNuxeoHome(), mpDir);
1595    }
1596
1597    /**
1598     * @return Install/upgrade file
1599     * @since 5.4.1
1600     */
1601    public File getInstallFile() {
1602        return new File(serverConfigurator.getDataDir(), INSTALL_AFTER_RESTART);
1603    }
1604
1605    /**
1606     * Add template(s) to the {@link #PARAM_TEMPLATES_NAME} list if not already present
1607     *
1608     * @param templatesToAdd Comma separated templates to add
1609     * @throws ConfigurationException
1610     * @since 5.5
1611     */
1612    public void addTemplate(String templatesToAdd) throws ConfigurationException {
1613        String currentTemplatesStr = userConfig.getProperty(PARAM_TEMPLATES_NAME);
1614        List<String> templatesList = new ArrayList<>();
1615        templatesList.addAll(Arrays.asList(currentTemplatesStr.split(TEMPLATE_SEPARATOR)));
1616        List<String> templatesToAddList = Arrays.asList(templatesToAdd.split(TEMPLATE_SEPARATOR));
1617        if (templatesList.addAll(templatesToAddList)) {
1618            String newTemplatesStr = StringUtils.join(templatesList, TEMPLATE_SEPARATOR);
1619            HashMap<String, String> parametersToSave = new HashMap<>();
1620            parametersToSave.put(PARAM_TEMPLATES_NAME, newTemplatesStr);
1621            saveFilteredConfiguration(parametersToSave);
1622            changeTemplates(newTemplatesStr);
1623        }
1624    }
1625
1626    /**
1627     * Remove template(s) from the {@link #PARAM_TEMPLATES_NAME} list
1628     *
1629     * @param templatesToRm Comma separated templates to remove
1630     * @throws ConfigurationException
1631     * @since 5.5
1632     */
1633    public void rmTemplate(String templatesToRm) throws ConfigurationException {
1634        String currentTemplatesStr = userConfig.getProperty(PARAM_TEMPLATES_NAME);
1635        List<String> templatesList = new ArrayList<>();
1636        templatesList.addAll(Arrays.asList(currentTemplatesStr.split(TEMPLATE_SEPARATOR)));
1637        List<String> templatesToRmList = Arrays.asList(templatesToRm.split(TEMPLATE_SEPARATOR));
1638        if (templatesList.removeAll(templatesToRmList)) {
1639            String newTemplatesStr = StringUtils.join(templatesList, TEMPLATE_SEPARATOR);
1640            HashMap<String, String> parametersToSave = new HashMap<>();
1641            parametersToSave.put(PARAM_TEMPLATES_NAME, newTemplatesStr);
1642            saveFilteredConfiguration(parametersToSave);
1643            changeTemplates(newTemplatesStr);
1644        }
1645    }
1646
1647    /**
1648     * Set a property in nuxeo configuration
1649     *
1650     * @param key
1651     * @param value
1652     * @throws ConfigurationException
1653     * @return The old value
1654     * @since 5.5
1655     */
1656    public String setProperty(String key, String value) throws ConfigurationException {
1657        String oldValue = getStoredConfig().getProperty(key);
1658        if (PARAM_TEMPLATES_NAME.equals(key)) {
1659            templates = StringUtils.isBlank(value) ? null : value;
1660        }
1661        HashMap<String, String> newParametersToSave = new HashMap<>();
1662        newParametersToSave.put(key, value);
1663        saveFilteredConfiguration(newParametersToSave);
1664        setBasicConfiguration();
1665        return oldValue;
1666    }
1667
1668    /**
1669     * Set properties in nuxeo configuration
1670     *
1671     * @param newParametersToSave
1672     * @return The old values
1673     * @throws ConfigurationException
1674     * @since 7.4
1675     */
1676    public Map<String, String> setProperties(Map<String, String> newParametersToSave) throws ConfigurationException {
1677        Map<String, String> oldValues = new HashMap<>();
1678        for (String key : newParametersToSave.keySet()) {
1679            oldValues.put(key, getStoredConfig().getProperty(key));
1680            if (PARAM_TEMPLATES_NAME.equals(key)) {
1681                String value = newParametersToSave.get(key);
1682                templates = StringUtils.isBlank(value) ? null : value;
1683            }
1684        }
1685        saveFilteredConfiguration(newParametersToSave);
1686        setBasicConfiguration();
1687        return oldValues;
1688    }
1689
1690    /**
1691     * Set properties in the given template, if it exists
1692     *
1693     * @param newParametersToSave
1694     * @return The old values
1695     * @throws ConfigurationException
1696     * @throws IOException
1697     * @since 7.4
1698     */
1699    public Map<String, String> setProperties(String template, Map<String, String> newParametersToSave)
1700            throws ConfigurationException, IOException {
1701        File templateConf = getTemplateConf(template);
1702        Properties templateProperties = loadTrimmedProperties(templateConf);
1703        Map<String, String> oldValues = new HashMap<>();
1704        StringBuffer newContent = new StringBuffer();
1705        try (BufferedReader reader = new BufferedReader(new FileReader(templateConf))) {
1706            String line = reader.readLine();
1707            if (line != null && line.startsWith("## DO NOT EDIT THIS FILE")) {
1708                throw new ConfigurationException("The template states in its header that it must not be modified.");
1709            }
1710            while (line != null) {
1711                int equalIdx = line.indexOf("=");
1712                if (equalIdx < 1 || line.trim().startsWith("#")) {
1713                    newContent.append(line + System.getProperty("line.separator"));
1714                } else {
1715                    String key = line.substring(0, equalIdx).trim();
1716                    if (newParametersToSave.containsKey(key)) {
1717                        newContent.append(key + "=" + newParametersToSave.get(key)
1718                                + System.getProperty("line.separator"));
1719                    } else {
1720                        newContent.append(line + System.getProperty("line.separator"));
1721                    }
1722                }
1723                line = reader.readLine();
1724            }
1725        }
1726        for (String key : newParametersToSave.keySet()) {
1727            if (templateProperties.containsKey(key)) {
1728                oldValues.put(key, templateProperties.getProperty(key));
1729            } else {
1730                newContent.append(key + "=" + newParametersToSave.get(key) + System.getProperty("line.separator"));
1731            }
1732        }
1733        try (BufferedWriter writer = new BufferedWriter(new FileWriter(templateConf))) {
1734            writer.append(newContent.toString());
1735        }
1736        setBasicConfiguration();
1737        return oldValues;
1738    }
1739
1740    /**
1741     * Check driver availability and database connection
1742     *
1743     * @param databaseTemplate Nuxeo database template
1744     * @param dbName nuxeo.db.name parameter in nuxeo.conf
1745     * @param dbUser nuxeo.db.user parameter in nuxeo.conf
1746     * @param dbPassword nuxeo.db.password parameter in nuxeo.conf
1747     * @param dbHost nuxeo.db.host parameter in nuxeo.conf
1748     * @param dbPort nuxeo.db.port parameter in nuxeo.conf
1749     * @throws DatabaseDriverException
1750     * @throws IOException
1751     * @throws FileNotFoundException
1752     * @throws SQLException
1753     * @since 5.6
1754     */
1755    public void checkDatabaseConnection(String databaseTemplate, String dbName, String dbUser, String dbPassword,
1756            String dbHost, String dbPort) throws FileNotFoundException, IOException, DatabaseDriverException,
1757            SQLException {
1758        File databaseTemplateDir = new File(nuxeoHome, TEMPLATES + File.separator + databaseTemplate);
1759        Properties templateProperties = loadTrimmedProperties(new File(databaseTemplateDir, NUXEO_DEFAULT_CONF));
1760        String classname, connectionUrl;
1761        if (userConfig.getProperty(PARAM_TEMPLATE_DBNAME).equals(databaseTemplateDir)) {
1762            // userConfig already includes databaseTemplate
1763            classname = userConfig.getProperty(PARAM_DB_DRIVER);
1764            connectionUrl = userConfig.getProperty(PARAM_DB_JDBC_URL);
1765        } else { // testing a databaseTemplate not included in userConfig
1766            // check if value is set in nuxeo.conf
1767            if (userConfig.containsKey(PARAM_DB_DRIVER)) {
1768                classname = (String) userConfig.get(PARAM_DB_DRIVER);
1769            } else {
1770                classname = templateProperties.getProperty(PARAM_DB_DRIVER);
1771            }
1772            if (userConfig.containsKey(PARAM_DB_JDBC_URL)) {
1773                connectionUrl = (String) userConfig.get(PARAM_DB_JDBC_URL);
1774            } else {
1775                connectionUrl = templateProperties.getProperty(PARAM_DB_JDBC_URL);
1776            }
1777        }
1778        // Load driver class from template or default lib directory
1779        Driver driver = lookupDriver(databaseTemplate, databaseTemplateDir, classname);
1780        // Test db connection
1781        DriverManager.registerDriver(driver);
1782        Properties ttProps = new Properties(userConfig);
1783        ttProps.put(PARAM_DB_HOST, dbHost);
1784        ttProps.put(PARAM_DB_PORT, dbPort);
1785        ttProps.put(PARAM_DB_NAME, dbName);
1786        ttProps.put(PARAM_DB_USER, dbUser);
1787        ttProps.put(PARAM_DB_PWD, dbPassword);
1788        TextTemplate tt = new TextTemplate(ttProps);
1789        String url = tt.processText(connectionUrl);
1790        Properties conProps = new Properties();
1791        conProps.put("user", dbUser);
1792        conProps.put("password", dbPassword);
1793        log.debug("Testing URL " + url + " with " + conProps);
1794        Connection con = driver.connect(url, conProps);
1795        con.close();
1796    }
1797
1798    /**
1799     * Build an {@link URLClassLoader} for the given databaseTemplate looking in the templates directory and in the
1800     * server lib directory, then looks for a driver
1801     *
1802     * @param databaseTemplate
1803     * @param databaseTemplateDir
1804     * @param classname Driver class name, defined by {@link #PARAM_DB_DRIVER}
1805     * @return Driver driver if found, else an Exception must have been raised.
1806     * @throws IOException
1807     * @throws FileNotFoundException
1808     * @throws DatabaseDriverException If there was an error when trying to instantiate the driver.
1809     * @since 5.6
1810     */
1811    private Driver lookupDriver(String databaseTemplate, File databaseTemplateDir, String classname)
1812            throws FileNotFoundException, IOException, DatabaseDriverException {
1813        File[] files = (File[]) ArrayUtils.addAll( //
1814                new File(databaseTemplateDir, "lib").listFiles(), //
1815                serverConfigurator.getServerLibDir().listFiles());
1816        List<URL> urlsList = new ArrayList<>();
1817        if (files != null) {
1818            for (File file : files) {
1819                if (file.getName().endsWith("jar")) {
1820                    try {
1821                        urlsList.add(new URL("jar:file:" + file.getPath() + "!/"));
1822                        log.debug("Added " + file.getPath());
1823                    } catch (MalformedURLException e) {
1824                        log.error(e);
1825                    }
1826                }
1827            }
1828        }
1829        URLClassLoader ucl = new URLClassLoader(urlsList.toArray(new URL[0]));
1830        try {
1831            return (Driver) Class.forName(classname, true, ucl).newInstance();
1832        } catch (InstantiationException e) {
1833            throw new DatabaseDriverException(e);
1834        } catch (IllegalAccessException e) {
1835            throw new DatabaseDriverException(e);
1836        } catch (ClassNotFoundException e) {
1837            throw new DatabaseDriverException(e);
1838        }
1839    }
1840
1841    /**
1842     * @since 5.6
1843     * @return an {@link Environment} initialized with a few basics
1844     */
1845    public Environment getEnv() {
1846        /*
1847         * It could be useful to initialize DEFAULT env in {@link #setBasicConfiguration()}... For now, the generated
1848         * {@link Environment} is not static.
1849         */
1850        if (env == null) {
1851            env = new Environment(getRuntimeHome());
1852            File distribFile = new File(new File(nuxeoHome, TEMPLATES), "common/config/distribution.properties");
1853            if (distribFile.exists()) {
1854                try {
1855                    env.loadProperties(loadTrimmedProperties(distribFile));
1856                } catch (FileNotFoundException e) {
1857                    log.error(e);
1858                } catch (IOException e) {
1859                    log.error(e);
1860                }
1861            }
1862            env.loadProperties(userConfig);
1863            env.setServerHome(getNuxeoHome());
1864            env.init();
1865            env.setData(userConfig.getProperty(Environment.NUXEO_DATA_DIR, "data"));
1866            env.setLog(userConfig.getProperty(Environment.NUXEO_LOG_DIR, "logs"));
1867            env.setTemp(userConfig.getProperty(Environment.NUXEO_TMP_DIR, "tmp"));
1868            env.setPath(PARAM_MP_DIR, getDistributionMPDir(), env.getServerHome());
1869            env.setPath(Environment.NUXEO_MP_DIR, getPackagesDir(), env.getServerHome());
1870        }
1871        return env;
1872    }
1873
1874    /**
1875     * @since 5.6
1876     * @param propsFile Properties file
1877     * @return new Properties containing trimmed keys and values read in {@code propsFile}
1878     * @throws IOException
1879     */
1880    public static Properties loadTrimmedProperties(File propsFile) throws IOException {
1881        Properties props = new Properties();
1882        FileInputStream propsIS = new FileInputStream(propsFile);
1883        try {
1884            loadTrimmedProperties(props, propsIS);
1885        } finally {
1886            propsIS.close();
1887        }
1888        return props;
1889    }
1890
1891    /**
1892     * @since 5.6
1893     * @param props Properties object to be filled
1894     * @param propsIS Properties InputStream
1895     * @throws IOException
1896     */
1897    public static void loadTrimmedProperties(Properties props, InputStream propsIS) throws IOException {
1898        if (props == null) {
1899            return;
1900        }
1901        Properties p = new Properties();
1902        p.load(propsIS);
1903        @SuppressWarnings("unchecked")
1904        Enumeration<String> pEnum = (Enumeration<String>) p.propertyNames();
1905        while (pEnum.hasMoreElements()) {
1906            String key = pEnum.nextElement();
1907            String value = p.getProperty(key);
1908            props.put(key.trim(), value.trim());
1909        }
1910    }
1911
1912    /**
1913     * @return The generated properties file with dumped configuration.
1914     * @since 5.6
1915     */
1916    public File getDumpedConfig() {
1917        return new File(getConfigDir(), CONFIGURATION_PROPERTIES);
1918    }
1919
1920    /**
1921     * Build a {@link Hashtable} which contains environment properties to instantiate a {@link InitialDirContext}
1922     *
1923     * @since 6.0
1924     */
1925    public Hashtable<Object, Object> getContextEnv(String ldapUrl, String bindDn, String bindPassword,
1926            boolean checkAuthentication) {
1927        Hashtable<Object, Object> contextEnv = new Hashtable<>();
1928        contextEnv.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
1929        contextEnv.put("com.sun.jndi.ldap.connect.timeout", "10000");
1930        contextEnv.put(javax.naming.Context.PROVIDER_URL, ldapUrl);
1931        if (checkAuthentication) {
1932            contextEnv.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple");
1933            contextEnv.put(javax.naming.Context.SECURITY_PRINCIPAL, bindDn);
1934            contextEnv.put(javax.naming.Context.SECURITY_CREDENTIALS, bindPassword);
1935        }
1936        return contextEnv;
1937    }
1938
1939    /**
1940     * Check if the LDAP parameters are correct to bind to a LDAP server. if authenticate argument is true, it will also
1941     * check if the authentication against the LDAP server succeeds
1942     *
1943     * @param ldapUrl
1944     * @param ldapBindDn
1945     * @param ldapBindPwd
1946     * @param authenticate Indicates if authentication against LDAP should be checked.
1947     * @since 6.0
1948     */
1949    public void checkLdapConnection(String ldapUrl, String ldapBindDn, String ldapBindPwd, boolean authenticate)
1950            throws NamingException {
1951        checkLdapConnection(getContextEnv(ldapUrl, ldapBindDn, ldapBindPwd, authenticate));
1952    }
1953
1954    /**
1955     * @param contextEnv Environment properties to build a {@link InitialDirContext}
1956     * @since 6.0
1957     */
1958    public void checkLdapConnection(Hashtable<Object, Object> contextEnv) throws NamingException {
1959        DirContext dirContext = new InitialDirContext(contextEnv);
1960        dirContext.close();
1961    }
1962
1963    /**
1964     * @return a {@link Crypto} instance initialized with the configuration parameters
1965     * @since 7.4
1966     * @see Crypto
1967     */
1968    public Crypto getCrypto() {
1969        return userConfig.getCrypto();
1970    }
1971
1972    /**
1973     * @param template path to configuration template directory
1974     * @return A {@code nuxeo.defaults} file if it exists.
1975     * @throws ConfigurationException if the template file is not found.
1976     * @since 7.4
1977     */
1978    public File getTemplateConf(String template) throws ConfigurationException {
1979        File templateDir = new File(template);
1980        if (!templateDir.isAbsolute()) {
1981            templateDir = new File(System.getProperty("user.dir"), template);
1982            if (!templateDir.exists() || !new File(templateDir, NUXEO_DEFAULT_CONF).exists()) {
1983                templateDir = new File(nuxeoDefaultConf.getParentFile(), template);
1984            }
1985        }
1986        if (!templateDir.exists() || !new File(templateDir, NUXEO_DEFAULT_CONF).exists()) {
1987            throw new ConfigurationException("Template not found: " + template);
1988        }
1989        return new File(templateDir, NUXEO_DEFAULT_CONF);
1990    }
1991
1992    /**
1993     * Gets the Java options with 'nuxeo.*' properties substituted. It enables
1994     * usage of property like ${nuxeo.log.dir} inside JAVA_OPTS.
1995     *
1996     * @return the java options string.
1997     */
1998    protected String getJavaOpts(String key, String value) {
1999        return StrSubstitutor.replace(System.getProperty(key, value), getUserConfig());
2000    }
2001
2002}