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