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