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